# Ralph Progress Log

## Codebase Patterns
- Use `scripts/cargo/check-rivetkit-core-wasm.sh` as the canonical wasm dependency gate; it runs the wasm `cargo check`, scans `cargo tree -e normal`, checks the feature graph, and asserts native transport/runtime fail on wasm.
- vbare protocol schemas using hashable maps cannot contain raw `f64` fields because generated Rust derives `Eq` and `Hash`; encode floats as fixed bytes or an ordered wrapper.
- Envoy protocol version gates should return `versioned::ProtocolCompatibilityError` so callers can downcast compatibility failures and map them to user-facing unavailable errors.
- Shared SQLite bind/result/route types live in `rivetkit-sqlite-types`; `rivetkit-sqlite::query` and `rivetkit-core::actor::sqlite` re-export them for compatibility.
- Envoy-client tracks remote SQLite exec/execute requests separately from page-I/O SQLite requests; both queues must drain with `EnvoyShutdownError` on lost envoy or shutdown cleanup.
- Spawned runtime futures that need tracing assertions should carry the current dispatch with `.with_subscriber(...)`; `.in_current_span()` alone does not preserve a test subscriber across `tokio::spawn`.
- Pegboard-envoy remote SQL should reuse `rivetkit-sqlite::database::open_database_from_engine` so execution goes through `NativeDatabaseHandle` and the existing SQLite routing policy instead of direct `rusqlite` calls.
- Pegboard-envoy remote SQL executor cache entries use `Arc<OnceCell<NativeDatabaseHandle>>` so concurrent first SQL requests share one lazy executor per `(actor_id, sqlite_generation)`.
- Pegboard-envoy remote SQL work runs in bounded per-connection worker tasks and tracks in-flight requests by `(actor_id, sqlite_generation)` so actor close can wait before closing SQLite.
- Sent remote SQL requests fail with `sqlite.remote_indeterminate_result` on WebSocket disconnect; only unsent remote SQL requests may be sent after reconnect.
- TypeScript `db({ onMigrate })` runs migrations through `SqliteDatabase.writeMode`, so every `client.execute(...)` inside migration callbacks is forced through write execution for remote SQLite parity.
- `rivetkit-sqlite` integration tests can use `open_database_from_engine` to exercise the same server-side executor path used by pegboard-envoy remote SQLite.
- SQLite-specific driver suites opt into `SQLITE_DRIVER_MATRIX_OPTIONS`; backend selection flows from driver config to `RIVETKIT_TEST_SQLITE_BACKEND`, `registry.config.test.sqliteBackend`, and `JsActorConfig.remoteSqlite`.
- `rivet-envoy-client` transport features are mutually exclusive; native builds use default features, while wasm builds must disable defaults and enable `wasm-transport`.
- `rivet-envoy-client` keeps wasm WebSocket code behind `target_arch = "wasm32"` and a native-host stub behind `wasm-transport` so developer feature checks do not compile browser APIs.
- `rivetkit-core` runtime features are mutually exclusive; use `native-runtime` for native transport/process support and `wasm-runtime,sqlite-remote` for wasm remote-SQLite builds.
- `rivet-envoy-client::async_counter::AsyncCounter` owns the shared HTTP request counter type consumed by core sleep logic, avoiding a broad `rivet-util` dependency in wasm core builds.
- Crates that compile to `wasm32-unknown-unknown` and generate random IDs or jitter should enable `getrandom/js` plus `uuid/js` on the wasm target, while keeping workspace Tokio/UUID on native targets.
- `rivetkit-core` tests use Tokio paused time; keep `tokio/test-util` as a dev-only feature so no-default feature tests compile without changing runtime dependencies.
- Core-owned lifecycle tasks in `rivetkit-core` should spawn through `RuntimeSpawner` so native builds use Send-capable tasks and wasm builds use local tasks.
- TypeScript actor runtime code should use `CoreRuntime` from `rivetkit/src/registry/runtime.ts`; raw native or wasm binding imports stay in `src/registry/*-runtime.ts` and are guarded by `tests/runtime-import-guard.test.ts`.
- `@rivetkit/rivetkit-wasm` keeps wasm-pack output under `packages/rivetkit-wasm/pkg/` generated; source exports the raw WebSocket handle as `WebSocketHandle` to avoid shadowing the host `WebSocket` global.
- The wasm runtime adapter normalizes raw `Uint8Array` handle payloads back to `Buffer` at `src/registry/wasm-runtime.ts`, keeping shared registry code backend-neutral with the NAPI path.
- Wasm host smoke tests should drive `buildNativeFactory` through `WasmCoreRuntime` fake bindings so actor callbacks, KV, state serialization, remote SQLite routing, and NAPI import boundaries stay covered without requiring generated wasm-pack output.

Started: Wed Apr 29 08:03:50 PM PDT 2026
---
## 2026-04-29 22:47:42 PDT - US-017
- Added `scripts/cargo/check-rivetkit-core-wasm.sh` as the repeatable wasm build gate for `rivetkit-core`.
- The gate runs the wasm target `cargo check`, scans the normal wasm dependency tree for native-only crates, checks the feature graph for native runtime/transport leaks, and verifies native envoy/core runtime feature selections fail on `wasm32-unknown-unknown`.
- Documented the gate in `.agent/specs/rivetkit-core-wasm-support.md` and added the reusable command to `AGENTS.md`/`CLAUDE.md`.
- Files changed: `.agent/specs/rivetkit-core-wasm-support.md`, `AGENTS.md`/`CLAUDE.md`, `scripts/cargo/check-rivetkit-core-wasm.sh`, `scripts/ralph/prd.json`, `scripts/ralph/progress.txt`.
- Quality checks: `scripts/cargo/check-rivetkit-core-wasm.sh`, `cargo check -p rivetkit-core`.
- **Learnings for future iterations:**
  - Use the wasm gate script instead of hand-running only `cargo check`; it also catches normal dependency leaks and accidental native feature selection.
  - Scan wasm production dependencies with `cargo tree -e normal` so dev-dependencies do not create false native-crate failures.
  - Negative wasm checks are useful here: native transport/runtime compiling for `wasm32-unknown-unknown` should fail rather than silently becoming part of the wasm path.
---
## 2026-04-29 22:45:05 PDT - US-016
- Added `rivetkit-core::runtime` with `RuntimeSpawner`, `RuntimeBoxFuture`, and `boxed_runtime_future` so native builds keep Send-capable task spawning while wasm builds can compile local futures for JS-promise style callbacks.
- Routed core actor lifecycle spawn sites through `RuntimeSpawner`, including `ActorTask` run-handler startup, core-dispatched hook replies, registry actor task startup, pending startup stop handoff, and envoy stop completion handoff.
- Added a wasm-runtime compile test proving the boxed runtime future accepts an `Rc`/`RefCell` local callback without requiring `Send`.
- Files changed: `CLAUDE.md`/`AGENTS.md`, `rivetkit-rust/packages/rivetkit-core/src/runtime.rs`, `rivetkit-rust/packages/rivetkit-core/src/lib.rs`, `rivetkit-rust/packages/rivetkit-core/src/actor/task.rs`, `rivetkit-rust/packages/rivetkit-core/src/registry/mod.rs`, `rivetkit-rust/packages/rivetkit-core/src/registry/envoy_callbacks.rs`, `scripts/ralph/prd.json`, `scripts/ralph/progress.txt`.
- Quality checks: `cargo check -p rivetkit-core --no-default-features --features wasm-runtime,sqlite-remote`, `cargo test -p rivetkit-core runtime --no-default-features --features wasm-runtime,sqlite-remote -- --nocapture`, `cargo check -p rivetkit-core`, `cargo check -p rivetkit-core --target wasm32-unknown-unknown --no-default-features --features wasm-runtime,sqlite-remote`, `cargo test -p rivetkit-core lifecycle -- --nocapture`, `cargo test -p rivetkit-core actor_task -- --nocapture`.
- `cargo check -p rivetkit-core --no-default-features` fails because `rivet-envoy-client` intentionally requires either `native-transport` or `wasm-transport`.
- **Learnings for future iterations:**
  - Use `RuntimeSpawner` for core-owned lifecycle tasks instead of direct `tokio::spawn` when the task may need to run under `wasm-runtime`.
  - Use `RuntimeBoxFuture` or `boxed_runtime_future` for future wasm host callbacks that wrap local JS promises or closures and should not require `Send`.
  - Bare `--no-default-features` is not a valid core check after the envoy transport split; choose `native-runtime` or `wasm-runtime,sqlite-remote`.
---
## 2026-04-29 22:19:45 PDT - US-013
- Implemented the wasm envoy WebSocket transport with `web_sys::WebSocket`, `wasm_bindgen` event closures, `ArrayBuffer` decoding, binary sends, close handling, and host `setTimeout`-based reconnect sleeps.
- Shared native metadata, URL, ping/pong, and message-forwarding helpers with the wasm transport while keeping the existing native behavior unchanged.
- Preserved the same envoy URL query parameters and subprotocol auth shape as native, and checked the current official Cloudflare Workers and Deno WebSocket APIs for constructor, subprotocol, and `binaryType = "arraybuffer"` compatibility.
- Files changed: `AGENTS.md`/`CLAUDE.md`, `engine/sdks/rust/envoy-client/src/connection/mod.rs`, `engine/sdks/rust/envoy-client/src/connection/wasm.rs`, `scripts/ralph/prd.json`, `scripts/ralph/progress.txt`.
- Quality checks: `cargo check -p rivet-envoy-client --no-default-features --features wasm-transport`, `cargo check -p rivet-envoy-client`, `cargo test -p rivet-envoy-client`.
- `cargo check -p rivet-envoy-client --target wasm32-unknown-unknown --no-default-features --features wasm-transport` still fails before reaching envoy-client because `mio` is pulled into the wasm dependency tree through the wider Tokio/rivet-util graph.
- **Learnings for future iterations:**
  - Use `wasm_bindgen_futures::spawn_local` for the wasm connection loop because browser WebSocket handles and closures are local JavaScript objects.
  - Set `WebSocket.binaryType` to `ArrayBuffer` and decode inbound `MessageEvent` payloads through `js_sys::Uint8Array` before vbare protocol decoding.
  - Prefer global `setTimeout` for wasm transport reconnect delays so the transport matches Cloudflare Worker and Deno/Supabase host APIs without depending on native timer behavior.
---
## 2026-04-29 22:15:02 PDT - US-012
- Split `rivet-envoy-client` WebSocket transport selection into `connection/mod.rs`, `connection/native.rs`, and a compileable `connection/wasm.rs` placeholder.
- Added mutually exclusive `native-transport` and `wasm-transport` features, kept native transport as the default, and made `rustls` plus `tokio-tungstenite` optional behind `native-transport`.
- Added optional wasm transport dependencies for `wasm-bindgen`, `wasm-bindgen-futures`, `js-sys`, and `web-sys`.
- Files changed: `CLAUDE.md`, `Cargo.lock`, `engine/sdks/rust/envoy-client/Cargo.toml`, `engine/sdks/rust/envoy-client/src/connection/mod.rs`, `engine/sdks/rust/envoy-client/src/connection/native.rs`, `engine/sdks/rust/envoy-client/src/connection/wasm.rs`, `scripts/ralph/prd.json`, `scripts/ralph/progress.txt`.
- Quality checks: `cargo check -p rivet-envoy-client`, `cargo check -p rivet-envoy-client --no-default-features --features native-transport`, `cargo check -p rivet-envoy-client --no-default-features --features wasm-transport`, `cargo test -p rivet-envoy-client`, `cargo check -p rivet-test-envoy`, `cargo check -p rivetkit-core`, `cargo check -p rivetkit-sqlite`.
- `cargo check -p rivet-envoy-client --target wasm32-unknown-unknown --no-default-features --features wasm-transport` still fails because `rivet-util` pulls workspace `tokio` with native `mio`; that wider dependency gate belongs to the later core wasm gating stories.
- **Learnings for future iterations:**
  - Keep the public `connection::start_connection(shared)` and `connection::ws_send(...)` surface stable so actor, KV, SQLite, tunnel, and event modules do not care which transport feature is active.
  - Downstream wasm consumers must set `default-features = false` on `rivet-envoy-client`; enabling `wasm-transport` on top of defaults intentionally hits the mutually exclusive feature compile error.
  - `rivet-util` is still a wasm-target blocker for envoy-client because it brings native `tokio`/`mio` through the workspace dependency graph.
---
## 2026-04-29 22:09:23 PDT - US-011
- Expanded the SQLite driver matrix with runtime and SQLite backend dimensions, including native/local, native/remote, and skipped wasm/remote cells across bare, CBOR, and JSON encodings.
- Threaded the native remote-SQLite backend option through driver runtime env, registry test config, NAPI actor config, and core actor config.
- Added a remote SQLite lifecycle probe that proves executor creation stays lazy until SQL runs and reopens after actor sleep.
- Fixed pegboard-envoy remote SQL namespace validation to accept the connection's configured namespace name as well as its resolved namespace id.
- Reduced raw DB separation-test engine churn by keeping keyed handles while polling count assertions.
- Files changed: `engine/packages/pegboard-envoy/src/conn.rs`, `engine/packages/pegboard-envoy/src/ws_to_tunnel_task.rs`, `rivetkit-typescript/packages/rivetkit-napi/index.d.ts`, `rivetkit-typescript/packages/rivetkit-napi/src/actor_factory.rs`, `rivetkit-typescript/packages/rivetkit/fixtures/driver-test-suite/actor-db-raw.ts`, `rivetkit-typescript/packages/rivetkit/fixtures/driver-test-suite/registry-static.ts`, `rivetkit-typescript/packages/rivetkit/src/registry/config/index.ts`, `rivetkit-typescript/packages/rivetkit/src/registry/native.ts`, `rivetkit-typescript/packages/rivetkit/tests/driver/actor-db*.test.ts`, `rivetkit-typescript/packages/rivetkit/tests/driver/shared-harness.ts`, `rivetkit-typescript/packages/rivetkit/tests/driver/shared-matrix.ts`, `rivetkit-typescript/packages/rivetkit/tests/driver/shared-matrix.test.ts`, `rivetkit-typescript/packages/rivetkit/tests/driver/shared-types.ts`, `rivetkit-typescript/packages/rivetkit/tests/fixtures/driver-test-suite-runtime.ts`, `scripts/ralph/prd.json`, `scripts/ralph/progress.txt`.
- Quality checks: `cargo build -p rivet-engine`, `cargo check -p rivetkit-napi`, `pnpm --filter @rivetkit/rivetkit-napi build`, `pnpm --filter rivetkit check-types`, `pnpm --filter rivetkit run check:wait-for-comments`, `pnpm --filter rivetkit test tests/driver/shared-matrix.test.ts`, `pnpm --filter rivetkit test tests/driver/actor-db-raw.test.ts`, `pnpm --filter rivetkit test tests/driver/actor-db-raw.test.ts --testNamePattern "runtime \\(native\\) / sqlite \\(remote\\) / encoding \\(bare\\).*Remote Database Executor Lifecycle"`, `pnpm --filter rivetkit test tests/driver/actor-db-raw.test.ts --testNamePattern "runtime \\(native\\) / sqlite \\(local\\) / encoding \\(bare\\).*maintains separate databases"`.
- **Learnings for future iterations:**
  - Remote SQLite requests from native runtime carry the configured namespace name, while pegboard-envoy resolves the connection to a namespace id; validation needs to treat both as the same connection namespace.
  - `destroy()` creates a new actor and an empty DB on the next `getOrCreate`; use `triggerSleep()` when testing executor cleanup across actor close/wake.
  - Reissuing `getOrCreate` inside `vi.waitFor` loops can amplify engine load under expanded matrix runs; keep handles stable unless the test specifically needs fresh lookup behavior.
  - The existing `rivetkit-sqlite` Rust 2024 unsafe-operation warnings still appear during checks and are not caused by this story.
---
## 2026-04-29 21:43:16 PDT - US-008
- Moved pegboard-envoy remote SQLite exec, execute, and execute_write handling off the WebSocket read loop into bounded per-connection worker tasks.
- Added per-`(actor_id, sqlite_generation)` in-flight counters so actor stop and connection shutdown wait for accepted remote SQL before closing SQLite.
- Rejected new remote SQL after an actor enters stopping, documented the selected stop behavior, and kept `ActorStateStopped` cleanup from blocking later WebSocket frames.
- Added focused tests for bounded remote SQL worker dispatch, in-flight stop waiting, executor cache cleanup, and persisted data across lazy executor reopen.
- Files changed: `.agent/specs/rivetkit-core-wasm-support.md`, `engine/packages/pegboard-envoy/src/conn.rs`, `engine/packages/pegboard-envoy/src/ws_to_tunnel_task.rs`, `engine/packages/pegboard-envoy/src/actor_lifecycle.rs`, `engine/packages/pegboard-envoy/tests/support/ws_to_tunnel_task.rs`, `scripts/ralph/prd.json`, `scripts/ralph/progress.txt`.
- Quality checks: `cargo test -p pegboard-envoy remote_sqlite -- --nocapture`, `cargo test -p pegboard-envoy ws_to_tunnel_task -- --nocapture`, `cargo check -p pegboard-envoy`.
- **Learnings for future iterations:**
  - Remote SQL requests should be counted as in-flight before worker permit acquisition so queued work is visible to actor close.
  - Actor stop now rejects new remote SQL once `ActiveActorState::Stopping` is set; already accepted requests may finish, and close waits up to the actor stop budget.
  - `ActorStateStopped` cleanup may wait on SQL drain, so it should run outside the WebSocket read loop.
  - The existing `rivetkit-sqlite` Rust 2024 unsafe-operation warnings still appear during pegboard-envoy checks and are not caused by this story.
---
## 2026-04-29 21:29:19 PDT - US-007
- Made pegboard-envoy remote SQLite executors lazy and actor-generation scoped with a shared `OnceCell` cache entry per `(actor_id, sqlite_generation)`.
- Added cache cleanup helpers for actor stop, serverless close, and connection shutdown paths.
- Added tests proving executor cache entries are lazy, reused for the same generation, removed on actor-scoped cleanup, and recreated with persisted contents after reopen.
- Files changed: `engine/packages/pegboard-envoy/src/conn.rs`, `engine/packages/pegboard-envoy/src/ws_to_tunnel_task.rs`, `engine/packages/pegboard-envoy/src/actor_lifecycle.rs`, `engine/packages/pegboard-envoy/tests/support/ws_to_tunnel_task.rs`, `scripts/ralph/prd.json`, `scripts/ralph/progress.txt`.
- Quality checks: `cargo test -p pegboard-envoy remote_sqlite_executor -- --nocapture`, `cargo test -p pegboard-envoy ws_to_tunnel_task -- --nocapture`, `cargo check -p pegboard-envoy`.
- **Learnings for future iterations:**
  - Use `OnceCell` inside the `scc::HashMap` value for async lazy initialization. Do not hold an `scc` entry guard across the database open await.
  - Removing a remote SQL executor cache entry is separate from closing the actor's `SqliteEngine` generation; actor lifecycle paths must do both.
  - The existing `rivetkit-sqlite` Rust 2024 unsafe-operation warnings still appear during pegboard-envoy checks and are not caused by this story.
---
## 2026-04-29 21:18:55 PDT - US-006
- Wired pegboard-envoy remote SQLite exec, execute, and execute_write protocol messages into server-side execution.
- Added namespace, actor, active generation, SQL size, bind parameter, and response payload validation for remote SQL requests.
- Exposed an engine-backed direct SQLite opener in `rivetkit-sqlite` so pegboard-envoy can execute through the shared native VFS/database routing layer.
- Added remote SQL result/bind conversion helpers, executor caching per `(actor_id, sqlite_generation)`, and cleanup on actor stop/shutdown paths.
- Files changed: `.agent/specs/rivetkit-core-wasm-support.md`, `AGENTS.md`/`CLAUDE.md`, `Cargo.lock`, `engine/packages/pegboard-envoy/Cargo.toml`, `engine/packages/pegboard-envoy/src/actor_lifecycle.rs`, `engine/packages/pegboard-envoy/src/conn.rs`, `engine/packages/pegboard-envoy/src/sqlite_runtime.rs`, `engine/packages/pegboard-envoy/src/ws_to_tunnel_task.rs`, `engine/packages/pegboard-envoy/tests/support/ws_to_tunnel_task.rs`, `rivetkit-rust/packages/rivetkit-sqlite/src/database.rs`, `rivetkit-rust/packages/rivetkit-sqlite/src/vfs.rs`, `scripts/ralph/prd.json`, `scripts/ralph/progress.txt`.
- Quality checks: `cargo test -p rivetkit-sqlite database::tests -- --nocapture`, `cargo test -p pegboard-envoy ws_to_tunnel_task -- --nocapture`, `cargo check -p rivetkit-sqlite`, `cargo check -p pegboard-envoy`.
- **Learnings for future iterations:**
  - `rivetkit-sqlite` already owns SQLite statement classification and read/write routing in `NativeDatabaseHandle`; remote server-side execution should open a direct engine-backed VFS instead of reimplementing classification in pegboard-envoy.
  - The remote SQL protocol uses the SQLite storage generation, so pegboard-envoy validates against `ActiveActor.sqlite_generation`, not the actor command generation.
  - `rivetkit-sqlite` still emits pre-existing Rust 2024 unsafe-operation warnings during checks; they are warnings, not story failures.
---
## 2026-04-29 21:06:43 PDT - US-005
- Added `SqliteBackend::{LocalNative, RemoteEnvoy, Unavailable}` selection in `rivetkit-core::actor::sqlite`.
- Routed `exec`, `query`, `run`, `execute`, and `execute_write` through local native SQLite or remote envoy SQL while preserving public method signatures and the existing `SqliteDb::new(...)` constructor.
- Added explicit `remote_sqlite` actor config selection, structured remote SQLite errors, protocol bind/result conversion helpers, and focused backend/conversion/error tests.
- Fixed `ActorTask` spawned runtime tracing dispatch propagation so actor-event drain logs reach tracing assertions.
- Files changed: `rivetkit-rust/packages/rivetkit-core/src/actor/config.rs`, `rivetkit-rust/packages/rivetkit-core/src/actor/mod.rs`, `rivetkit-rust/packages/rivetkit-core/src/actor/sqlite.rs`, `rivetkit-rust/packages/rivetkit-core/src/actor/task.rs`, `rivetkit-rust/packages/rivetkit-core/src/error.rs`, `rivetkit-rust/packages/rivetkit-core/src/lib.rs`, `rivetkit-rust/packages/rivetkit-core/src/registry/mod.rs`, `rivetkit-rust/packages/rivetkit-core/tests/sqlite.rs`, `rivetkit-typescript/packages/rivetkit-napi/src/actor_factory.rs`, `rivetkit-rust/engine/artifacts/errors/sqlite.remote_execution_failed.json`, `rivetkit-rust/engine/artifacts/errors/sqlite.remote_fence_mismatch.json`, `rivetkit-rust/engine/artifacts/errors/sqlite.remote_unavailable.json`, `scripts/ralph/prd.json`, `scripts/ralph/progress.txt`.
- Quality checks: `cargo test -p rivetkit-core sqlite --no-default-features`, `cargo test -p rivetkit-core sqlite --features sqlite`, `cargo check -p rivetkit-core --no-default-features`, `cargo check -p rivetkit-core --features sqlite`, `cargo check -p rivetkit-napi`, `cargo test -p rivetkit-core actor::task::tests::moved_tests::actor_task_logs_lifecycle_dispatch_and_actor_event_flow --no-default-features -- --exact --nocapture`.
- Full `cargo test -p rivetkit-core --no-default-features` still fails under parallel execution on `actor_task_logs_lifecycle_dispatch_and_actor_event_flow` even though that exact test passes alone; the run also hangs afterward and was stopped.
- **Learnings for future iterations:**
  - Keep `SqliteDb::new(...)` source-compatible; use a separate constructor when threading new backend selection inputs through registry wiring.
  - Remote SQLite float values are encoded as fixed 8-byte `f64::to_bits().to_be_bytes()` payloads in the envoy protocol conversion helpers.
  - Structured SQLite error variants generate checked-in artifacts under `rivetkit-rust/engine/artifacts/errors/`.
  - Full core test runs can expose parallel tracing-test interference even when exact tests pass; focused story checks were stable here.
---
## 2026-04-29 20:31:48 PDT - US-002
- Added structured `ProtocolCompatibilityError` metadata for versioned envoy-protocol compatibility failures, including remote SQL request/response gates below protocol v4.
- Added remote SQL compatibility tests covering old core/new pegboard-envoy, new core/old pegboard-envoy, old core/old pegboard-envoy, new core/new pegboard-envoy, and all exec/execute/execute_write request and response variants.
- Documented mixed-version remote SQL behavior in `.agent/specs/rivetkit-core-wasm-support.md`.
- Files changed: `engine/sdks/rust/envoy-protocol/src/versioned.rs`, `engine/sdks/rust/envoy-protocol/tests/remote_sql_compat.rs`, `.agent/specs/rivetkit-core-wasm-support.md`, `scripts/ralph/prd.json`, `scripts/ralph/progress.txt`.
- Quality checks: `cargo test -p rivet-envoy-protocol`, `cargo check -p rivet-envoy-protocol`, `cargo check -p rivet-envoy-client`, `cargo check -p pegboard-envoy`.
- **Learnings for future iterations:**
  - Protocol compatibility rejections happen at `serialize_version(...)`, before an unsupported variant can become an older-version BARE payload.
  - Integration tests can exercise `generated::v4` plus `versioned::{ToRivet, ToEnvoy}` directly for rollout-matrix protocol coverage.
  - The repo may run out of disk during large Rust checks after many test artifacts accumulate; clearing rebuildable Cargo artifacts and stale `/tmp/rivet*` directories allowed checks to complete.
---
## 2026-04-29 20:18:43 PDT - US-001
- Added envoy protocol `v4.bare` with remote SQLite bind/value/result types and exec, execute, and execute_write request/response messages.
- Exported v4 as the latest Rust protocol, added v4 compatibility guards, regenerated the TypeScript envoy protocol artifact, and updated Rust stringifiers/downstream exhaustive matches for the new message variants.
- Files changed: `engine/sdks/schemas/envoy-protocol/v4.bare`, `engine/sdks/rust/envoy-protocol/src/lib.rs`, `engine/sdks/rust/envoy-protocol/src/versioned.rs`, `engine/sdks/typescript/envoy-protocol/src/index.ts`, `engine/sdks/rust/envoy-client/src/stringify.rs`, `engine/sdks/rust/envoy-client/src/envoy.rs`, `engine/packages/pegboard-envoy/src/ws_to_tunnel_task.rs`, `CLAUDE.md`, `.agent/specs/rivetkit-core-wasm-support.md`, `scripts/ralph/.last-branch`, `scripts/ralph/prd.json`, `scripts/ralph/progress.txt`, `scripts/ralph/archive/2026-04-29-rivetkit-core-wasm-support/`.
- Quality checks: `cargo check -p rivet-envoy-protocol`, `cargo check -p rivet-envoy-client`, `cargo test -p rivet-envoy-protocol`, `pnpm --filter @rivetkit/engine-envoy-protocol check-types`, `cargo check -p pegboard-envoy`.
- **Learnings for future iterations:**
  - The envoy protocol crate build script only regenerates checked-in TypeScript after root `node_modules` exists; run `pnpm install --frozen-lockfile` first in a fresh checkout.
  - Adding protocol union variants requires updating every Rust exhaustive match in envoy-client and pegboard-envoy, even before behavior is fully wired.
  - vbare hashable-map generation derives `Eq` and `Hash`, so raw `f64` schema fields break Rust generation.
---
## 2026-04-29 20:39:07 PDT - US-003
- Added `rivetkit-sqlite-types` for shared SQLite bind parameters, column values, query results, exec results, execute results, and execute routes.
- Re-exported the shared types from `rivetkit-sqlite::query` and `rivetkit-core::actor::sqlite`, removing the duplicated no-sqlite fallback definitions in core.
- Kept native routing behavior in `rivetkit-sqlite`, while using shared projection helpers for `query` and `run` results.
- Fixed the Rust wrapper's `ActorEvent::WebSocketOpen` match to acknowledge the current core event field set so the public wrapper typecheck passes.
- Files changed: `Cargo.toml`, `Cargo.lock`, `rivetkit-rust/packages/rivetkit-sqlite-types/`, `rivetkit-rust/packages/rivetkit-sqlite/src/query.rs`, `rivetkit-rust/packages/rivetkit-sqlite/src/database.rs`, `rivetkit-rust/packages/rivetkit-sqlite/src/lib.rs`, `rivetkit-rust/packages/rivetkit-sqlite/Cargo.toml`, `rivetkit-rust/packages/rivetkit-core/src/actor/sqlite.rs`, `rivetkit-rust/packages/rivetkit-core/Cargo.toml`, `rivetkit-rust/packages/rivetkit/src/event.rs`, `scripts/ralph/prd.json`, `scripts/ralph/progress.txt`.
- Quality checks: `cargo test -p rivetkit-sqlite-types`, `cargo check -p rivetkit-sqlite`, `cargo check -p rivetkit-core`, `cargo check -p rivetkit-core --features sqlite`, `cargo test -p rivetkit-sqlite query::tests`, `cargo check -p rivetkit`.
- **Learnings for future iterations:**
  - Keep statement classification and read/write routing in `rivetkit-sqlite`; shared types should stay plain and backend-neutral.
  - Core can depend on `rivetkit-sqlite-types` unconditionally, which avoids duplicating SQLite API result shapes when native SQLite is feature-gated out.
  - The native VFS currently emits many Rust 2024 unsafe-operation warnings during checks; they are pre-existing warnings, not failures.
---
## 2026-04-29 20:46:54 PDT - US-004
- Added remote SQLite exec, execute, and execute_write request/response tracking to envoy-client with a dedicated `ToEnvoyMessage::RemoteSqliteRequest` path.
- Wired `EnvoyHandle` methods for remote SQL, outbound `ToRivetSqlite*Request` messages, inbound response matching, reconnect unsent processing, timeout cleanup, and `EnvoyShutdownError` shutdown cleanup.
- Added envoy-client tests for successful response matching, protocol v3 rejection, and shutdown cleanup of pending remote SQL requests.
- Files changed: `engine/sdks/rust/envoy-client/src/envoy.rs`, `engine/sdks/rust/envoy-client/src/handle.rs`, `engine/sdks/rust/envoy-client/src/sqlite.rs`, `engine/sdks/rust/envoy-client/src/events.rs`, `engine/sdks/rust/envoy-client/tests/command_dedup.rs`, `scripts/ralph/prd.json`, `scripts/ralph/progress.txt`.
- Quality checks: `cargo test -p rivet-envoy-client sqlite::tests -- --nocapture`, `cargo check -p rivet-envoy-client`, `cargo test -p rivet-envoy-client`.
- **Learnings for future iterations:**
  - Remote SQL execution uses protocol v4 only; client-side stale-version tests can serialize the generated `ToRivetSqlite*Request` messages against v3 and downcast to `ProtocolCompatibilityError`.
  - Keep remote SQL request IDs in their own envoy-client map because response variants are disjoint from the existing SQLite page-I/O protocol.
  - Shutdown cleanup should use `EnvoyShutdownError` for pending SQLite queues so callers can detect envoy loss separately from SQLite execution errors.
---
## 2026-04-29 21:48:44 PDT - US-009
- Added `RemoteSqliteIndeterminateResultError` in envoy-client and fail sent remote SQL requests with it when the WebSocket disconnects.
- Left unsent remote SQL requests pending so they can send after reconnect, while removing sent requests to prevent blind replay.
- Mapped the typed envoy-client lost-response error into core's structured `sqlite.remote_indeterminate_result` error and checked in its error artifact.
- Documented the selected lost-response behavior in the wasm support spec and project notes.
- Files changed: `AGENTS.md`, `CLAUDE.md`, `.agent/specs/rivetkit-core-wasm-support.md`, `engine/sdks/rust/envoy-client/src/utils.rs`, `engine/sdks/rust/envoy-client/src/sqlite.rs`, `engine/sdks/rust/envoy-client/src/envoy.rs`, `engine/sdks/rust/envoy-client/tests/command_dedup.rs`, `rivetkit-rust/packages/rivetkit-core/src/error.rs`, `rivetkit-rust/packages/rivetkit-core/src/actor/sqlite.rs`, `rivetkit-rust/packages/rivetkit-core/tests/sqlite.rs`, `rivetkit-rust/engine/artifacts/errors/sqlite.remote_indeterminate_result.json`, `scripts/ralph/prd.json`, `scripts/ralph/progress.txt`.
- Quality checks: `cargo test -p rivet-envoy-client sqlite::tests -- --nocapture`, `cargo test -p rivet-envoy-client --test command_dedup -- --nocapture`, `cargo test -p rivetkit-core sqlite --no-default-features -- --nocapture`, `cargo test -p rivetkit-core sqlite --features sqlite -- --nocapture`, `cargo check -p rivet-envoy-client`, `cargo check -p rivetkit-core --no-default-features`, `cargo check -p rivetkit-core --features sqlite`.
- **Learnings for future iterations:**
  - Treat every sent remote SQL request as potentially write-affecting after a disconnect because `Execute` routing is decided by the shared SQLite executor on the server.
  - Only `sent == false` remote SQL entries are safe to process on reconnect.
  - The existing `rivetkit-sqlite` Rust 2024 unsafe-operation warnings still appear during core checks with the `sqlite` feature and are not caused by this story.
---
## 2026-04-29 21:53:43 PDT - US-010
- Added remote SQLite executor parity tests covering migration ordering across reopen, `execute_write` forcing the writer route for read-only SQL, and manual `BEGIN`, `SAVEPOINT`, `COMMIT`, and `ROLLBACK` behavior on the same remote database handle.
- Added a TypeScript database provider test proving `db({ onMigrate })` runs migration callbacks through `SqliteDatabase.writeMode`.
- Files changed: `rivetkit-rust/packages/rivetkit-sqlite/tests/remote_execution_parity.rs`, `rivetkit-typescript/packages/rivetkit/src/common/database/mod.test.ts`, `scripts/ralph/prd.json`, `scripts/ralph/progress.txt`.
- Quality checks: `cargo test -p rivetkit-sqlite --test remote_execution_parity -- --nocapture`, `cargo check -p rivetkit-sqlite`, `pnpm --filter @rivetkit/virtual-websocket build`, `pnpm --filter @rivetkit/engine-envoy-protocol build`, `pnpm --filter @rivetkit/workflow-engine build`, `pnpm --filter rivetkit test src/common/database/mod.test.ts`, `pnpm --filter rivetkit exec biome check src/common/database/mod.test.ts`, `pnpm --filter rivetkit check-types`.
- **Learnings for future iterations:**
  - `db({ onMigrate })` and Drizzle migrations rely on the shared `__rivetWriteMode` convention to force remote SQLite execution onto the writer path.
  - `execute_write` returns `ExecuteRoute::Write` even for read-only SQL, which is the easiest assertion that the forced-writer path is being used.
  - The RivetKit TypeScript typecheck may need workspace dependency packages built first so their `dist/*.d.ts` exports exist.
  - The existing `rivetkit-sqlite` Rust 2024 unsafe-operation warnings still appear during sqlite checks and are not caused by this story.
---
## 2026-04-29 22:31:53 PDT - US-014
- Added `rivetkit-core` runtime and SQLite feature gates: `native-runtime`, `wasm-runtime`, `sqlite-local`, and `sqlite-remote`, with the old `sqlite` feature kept as a compatibility alias for local native SQLite.
- Routed `native-runtime` to envoy-client native transport plus native process/runner-config dependencies, routed `wasm-runtime` to envoy-client wasm transport, and made `sqlite-local` native-only.
- Moved `AsyncCounter` ownership into `rivet-envoy-client` so core sleep logic can share envoy HTTP request counters without depending on broad `rivet-util`.
- Gated engine process startup and local runner-config HTTP setup behind `native-runtime`, with explicit errors when `engine_binary_path` is requested without native runtime support.
- Files changed: `AGENTS.md`/`CLAUDE.md`, `Cargo.toml`, `Cargo.lock`, `engine/sdks/rust/envoy-client/`, `engine/sdks/rust/test-envoy/Cargo.toml`, `rivetkit-rust/packages/rivetkit-core/`, `rivetkit-rust/packages/rivetkit-sqlite/Cargo.toml`, `scripts/ralph/prd.json`, `scripts/ralph/progress.txt`.
- Quality checks: `cargo check -p rivet-envoy-client --no-default-features --features wasm-transport`, `cargo check -p rivetkit-core --no-default-features --features wasm-runtime,sqlite-remote`, `cargo tree -p rivetkit-core --no-default-features --features wasm-runtime,sqlite-remote` with no matches for `rivetkit-sqlite`, `libsqlite3-sys`, `tokio-tungstenite`, `rivet-pools`, `rivet-util`, `reqwest`, or `nix`, `cargo check -p rivetkit-core`, `cargo check -p rivetkit-core --features sqlite`, `cargo check -p rivet-envoy-client`, `cargo test -p rivet-envoy-client active_http_request_counter -- --nocapture`, `cargo check -p rivetkit`, `cargo check -p rivetkit-sqlite`, `cargo check -p rivet-test-envoy`, `cargo test -p rivetkit-core sleep -- --nocapture`, `cargo check -p rivetkit-napi`.
- `cargo check -p rivetkit-core --target wasm32-unknown-unknown --no-default-features --features wasm-runtime,sqlite-remote` still fails on wasm-host `getrandom` and workspace Tokio `mio`; that full wasm build gate is US-017.
- **Learnings for future iterations:**
  - Core's wasm feature path now excludes the native SQLite crate, native WebSocket transport, `rivet-pools`, `rivet-util`, `reqwest`, and `nix` on the normal dependency tree.
  - Keep `sqlite` as a compatibility alias for `sqlite-local`; update cfg checks to `sqlite-local` so direct `sqlite-local` builds behave the same as legacy `sqlite`.
  - The envoy HTTP request counter is a cross-crate type contract between envoy-client and core sleep logic, so its shared type belongs in `rivet-envoy-client`.
---
## 2026-04-29 22:40:50 PDT - US-015
- Gated wasm core dependency selection with target-specific Tokio and UUID dependencies, plus the JS `getrandom` backend for wasm random ID generation.
- Fixed the wasm envoy transport helper paths so the real `wasm32-unknown-unknown` check reaches core instead of failing in the transport wrapper.
- Made synchronous queue receives fail with a structured `actor.invalid_operation` error on wasm instead of compiling a native-only `block_in_place` path.
- Added a no-native-runtime serverless test proving engine process spawning returns an explicit configuration error.
- Files changed: `CLAUDE.md`, `Cargo.lock`, `engine/sdks/rust/envoy-client/Cargo.toml`, `engine/sdks/rust/envoy-client/src/connection/wasm.rs`, `rivetkit-rust/packages/rivetkit-core/Cargo.toml`, `rivetkit-rust/packages/rivetkit-core/src/actor/queue.rs`, `rivetkit-rust/packages/rivetkit-core/tests/serverless.rs`, `scripts/ralph/prd.json`, `scripts/ralph/progress.txt`.
- Quality checks: `cargo check -p rivet-envoy-client --target wasm32-unknown-unknown --no-default-features --features wasm-transport`, `cargo check -p rivetkit-core --target wasm32-unknown-unknown --no-default-features --features wasm-runtime,sqlite-remote`, `cargo test -p rivetkit-core engine_process_spawn_requires_native_runtime --no-default-features --features wasm-runtime,sqlite-remote -- --nocapture`, `cargo check -p rivetkit-core`, `cargo test -p rivetkit-core serverless -- --nocapture`, `cargo check -p rivetkit-core --features sqlite`, and a wasm dependency tree scan with no matches for native SQLite, `libsqlite3-sys`, `tokio-tungstenite`, `mio`, `nix`, `rivet-pools`, `reqwest`, or `rivet-util`.
- **Learnings for future iterations:**
  - `cargo tree` includes dev-dependencies unless constrained with `-e normal`; use `-e normal` when checking the production wasm dependency tree.
  - The wasm envoy transport implementation is nested under `connection::wasm::imp`, so shared helpers in `connection/mod.rs` are reached through `super::super`.
  - Synchronous queue APIs are native-only when they require blocking the current runtime. Wasm builds should return explicit structured errors for those surfaces.
---
## 2026-04-29 23:00:09 PDT - US-019
- Added a bridge-neutral TypeScript `CoreRuntime` interface with opaque registry, actor factory, actor context, connection, WebSocket, and cancellation token handles.
- Moved NAPI-specific binding loading and class calls into `src/registry/napi-runtime.ts`, then routed registry/native actor adaptation through the runtime interface, including KV, SQLite, queue, schedule, WebSocket, cancellation, serverless, and inspector helpers.
- Added `tests/runtime-import-guard.test.ts` and moved the inspector versioning test off direct `@rivetkit/rivetkit-napi` imports.
- Files changed: `AGENTS.md`, `CLAUDE.md`, `rivetkit-typescript/packages/rivetkit/src/registry/index.ts`, `rivetkit-typescript/packages/rivetkit/src/registry/native.ts`, `rivetkit-typescript/packages/rivetkit/src/registry/runtime.ts`, `rivetkit-typescript/packages/rivetkit/src/registry/napi-runtime.ts`, `rivetkit-typescript/packages/rivetkit/tests/inspector-versioned.test.ts`, `rivetkit-typescript/packages/rivetkit/tests/runtime-import-guard.test.ts`, `scripts/ralph/prd.json`, `scripts/ralph/progress.txt`.
- Quality checks: `pnpm --filter rivetkit check-types`, `pnpm --filter rivetkit test tests/inspector-versioned.test.ts tests/runtime-import-guard.test.ts`, `pnpm --filter rivetkit exec biome check src/registry/runtime.ts src/registry/napi-runtime.ts src/registry/native.ts tests/inspector-versioned.test.ts tests/runtime-import-guard.test.ts`, `pnpm --filter rivetkit run check:test-skips`, `pnpm --filter rivetkit run check:wait-for-comments`.
- `pnpm --filter rivetkit lint` still fails on pre-existing fixture-wide Biome diagnostics under `fixtures/driver-test-suite/*`; touched files pass Biome.
- **Learnings for future iterations:**
  - The TypeScript runtime interface should expose explicit methods on opaque handles rather than leaking NAPI binding classes into shared actor adaptation code.
  - SQLite stays routed through `ActorContextHandle` methods on `CoreRuntime`; the NAPI adapter can cache the native `JsNativeDatabase` internally while shared code only sees the normalized database wrapper.
  - Direct imports of `@rivetkit/rivetkit-napi` or future `@rivetkit/rivetkit-wasm` outside runtime adapter files should fail the import guard test.
---
## 2026-04-29 23:08:29 PDT - US-020
- Added `@rivetkit/rivetkit-wasm` as a separate TypeScript package and Rust `cdylib` crate over `rivetkit-core` using direct wasm-bindgen.
- Exposed raw wasm handles for registry, actor factory, cancellation token, actor context, connection, and WebSocket handle, plus Uint8Array and Promise boundary helpers.
- Added wasm-pack build scripts for web/Deno and Cloudflare-style bundler targets while keeping native NAPI unchanged.
- Files changed: `Cargo.toml`, `Cargo.lock`, `package.json`, `pnpm-lock.yaml`, `rivetkit-typescript/CLAUDE.md`, `rivetkit-typescript/packages/rivetkit-wasm/`, `scripts/ralph/prd.json`, `scripts/ralph/progress.txt`.
- Quality checks: `cargo check -p rivetkit-wasm --target wasm32-unknown-unknown`, `cargo check -p rivetkit-wasm`, `cargo check -p rivetkit-napi`, `pnpm --filter @rivetkit/rivetkit-wasm check-types`, `pnpm --filter @rivetkit/rivetkit-wasm build`, `scripts/cargo/check-rivetkit-core-wasm.sh`.
- **Learnings for future iterations:**
  - Keep the wasm binding package source-only in git; `pkg/` is generated by wasm-pack during package builds.
  - wasm-bindgen rejects exported classes named `WebSocket`, so the raw wasm binding uses `WebSocketHandle`.
  - The initial wasm actor factory binds core registration and config parsing, while full JS callback dispatch belongs in the shared wasm adapter story.
---
## 2026-04-29 23:15:56 PDT - US-021
- Added `WasmCoreRuntime` in `rivetkit/src/registry/wasm-runtime.ts`, backed by `@rivetkit/rivetkit-wasm`, with registry/factory/cancellation handle mapping, bridge-error decoding, explicit unsupported-method failures, and Buffer normalization for wasm byte payloads.
- Added focused runtime adapter tests proving the wasm and NAPI adapters satisfy the same `CoreRuntime` interface, raw wasm handles are mapped through the adapter, structured wasm bridge errors decode to `RivetError`, and missing wasm exports fail explicitly.
- Added `@rivetkit/rivetkit-wasm` as a direct `rivetkit` package dependency and documented the wasm payload normalization convention.
- Files changed: `rivetkit-typescript/packages/rivetkit/src/registry/wasm-runtime.ts`, `rivetkit-typescript/packages/rivetkit/tests/wasm-runtime.test.ts`, `rivetkit-typescript/packages/rivetkit/package.json`, `pnpm-lock.yaml`, `rivetkit-typescript/CLAUDE.md`, `scripts/ralph/prd.json`, `scripts/ralph/progress.txt`.
- Quality checks: `pnpm --filter rivetkit check-types`, `pnpm --filter @rivetkit/rivetkit-wasm check-types`, `pnpm --filter rivetkit test tests/wasm-runtime.test.ts`, `pnpm --filter rivetkit test tests/runtime-import-guard.test.ts`, `pnpm --filter rivetkit exec biome check src/registry/wasm-runtime.ts tests/wasm-runtime.test.ts`, `pnpm --filter rivetkit run check:wait-for-comments`, `pnpm --filter rivetkit run check:test-skips`.
- **Learnings for future iterations:**
  - Keep raw `@rivetkit/rivetkit-wasm` imports inside `src/registry/wasm-runtime.ts`; `tests/runtime-import-guard.test.ts` enforces the same boundary as the NAPI adapter.
  - Wasm binding methods can return `Uint8Array`; normalize them to `Buffer` in the adapter before shared registry code sees them.
  - Until every raw wasm handle method exists, fail through structured `feature.unsupported` errors instead of silent no-ops.
---
## 2026-04-29 23:23:14 PDT - US-022
- Added Supabase Edge Functions/Deno and Cloudflare Workers wasm host smoke coverage through the shared `WasmCoreRuntime` interface.
- The smoke harness verifies envoy WebSocket URL fields, `rivet` plus `rivet_token.*` subprotocol auth, `arraybuffer` binary mode, actor action dispatch, state serialization, KV access, remote SQLite execute/write/query calls, and deterministic reconnect points during action and remote write SQL.
- Kept native NAPI separate by using the existing runtime import guard alongside the wasm-only smoke harness.
- Files changed: `CLAUDE.md`/`AGENTS.md`, `rivetkit-typescript/packages/rivetkit/tests/wasm-host-smoke.test.ts`, `scripts/ralph/prd.json`, `scripts/ralph/progress.txt`.
- Quality checks: `pnpm --filter rivetkit test tests/wasm-host-smoke.test.ts`, `pnpm --filter rivetkit exec biome check tests/wasm-host-smoke.test.ts`, `pnpm --filter rivetkit check-types`, `pnpm --filter rivetkit test tests/runtime-import-guard.test.ts`, `pnpm --filter rivetkit run check:wait-for-comments`, `pnpm --filter rivetkit run check:test-skips`.
- **Learnings for future iterations:**
  - The wasm host smoke can exercise shared TypeScript actor adaptation by building factories with `buildNativeFactory` and running them through `WasmCoreRuntime` fake bindings.
  - Public `c.sql` write forcing goes through `writeMode(() => c.sql.execute(...))`; the lower runtime adapter maps that to `executeWrite`.
  - `@rivetkit/rivetkit-wasm/pkg/` is generated, so host smoke tests should not require importing the real package until the wasm-pack output exists in the test environment.
---
## 2026-04-29 23:26:43 PDT - US-023
- Documented the implemented remote SQLite and wasm runtime invariants in `.agent/specs/rivetkit-core-wasm-support.md`.
- Refreshed stale current-state notes so the spec records v4-only remote SQL rollout behavior, wasm remote-only SQLite, lazy pegboard-envoy SQL executors, envoy-client transport ownership, lost-response behavior, and the direct wasm-bindgen binding strategy.
- Files changed: `.agent/specs/rivetkit-core-wasm-support.md`, `scripts/ralph/prd.json`, `scripts/ralph/progress.txt`.
- Quality checks: `scripts/cargo/check-rivetkit-core-wasm.sh`.
- **Learnings for future iterations:**
  - Keep high-level wasm and remote SQLite decisions in the spec's implemented invariants section so future changes do not have to reconstruct them from individual story logs.
  - The wasm support spec should reflect the current gate command instead of stale one-off compile probes.
  - Remote SQL lost-response behavior is now a decided invariant: sent requests fail with `sqlite.remote_indeterminate_result`, while only unsent requests can be sent after reconnect.
---
