# Ralph Progress Log
## Codebase Patterns
- The NAPI and wasm TypeScript adapters implement the shared `CoreRuntime` contract in `rivetkit-typescript/packages/rivetkit/src/registry/`.
- Keep raw `@rivetkit/rivetkit-napi` and `@rivetkit/rivetkit-wasm` imports inside runtime adapter modules (`napi-runtime.ts`, `wasm-runtime.ts`); enforced by `tests/runtime-import-guard.test.ts`.
- Wasm cannot use local SQLite. Valid SQLite runtime cells are native/local, native/remote, and wasm/remote.
- Cross-boundary error sanitization belongs in `rivetkit-core`. The TS bridge passes raw errors through; status code promotion should not live in `wasm-runtime.ts`.
- Use `rivetkit-typescript/packages/rivetkit/tests/shared-engine.ts` for any TS test that needs a local `rivet-engine`.
- The wasm crate uses `RuntimeSpawner::spawn` which maps to `tokio::task::spawn_local` under `wasm-runtime` and `tokio::spawn` otherwise.
- Bridged JS errors round-trip via `__RIVET_ERROR_JSON__:` prefix in the message string; decoded with `parse_bridge_rivet_error` and re-encoded with `anyhow_to_js_error`.
- Wasm `JsValue` callbacks are not Send/Sync; `WasmFunction` uses an unsafe Send/Sync impl because the wasm runtime is single-threaded.
- Wasm host smoke tests use fake `WasmBindings` in `rivetkit-typescript/packages/rivetkit/tests/wasm-host-smoke.test.ts` to exercise the TypeScript `WasmCoreRuntime` adapter without requiring a built wasm artifact.
- Wasm bindings that return `Result<_, JsValue>` should map core `anyhow::Error` values with `anyhow_to_js_error`.
- Wasm bridged RivetError schemas are process-interned with `scc::HashMap` by `(group, code)` only; live per-error messages stay on `RivetError.message`.
- Validate wasm-only `ServeConfig` constraints at wasm entrypoints before converting to core `ServeConfig` or starting registry side effects.
- Runtime-owned promises that must drain during shutdown use core `ActorContext::register_task(...)`, not public `wait_until(...)`, so NAPI and wasm can keep `registerTask` intent distinct.
- Public HTTP status promotion for bridged errors lives in `rivetkit-core::error::public_error_status_code`; TS native/wasm adapters should not duplicate promotion tables.
- `@rivetkit/rivetkit-wasm` builds must use the package-local pinned `wasm-pack` dependency; do not shell through `npx -y wasm-pack`.
- Runtime parity tests can instantiate `NapiCoreRuntime` and `WasmCoreRuntime` with fake binding classes, then drive shared actor glue through `buildNativeFactory(...)` without requiring generated NAPI or wasm artifacts.
- NAPI actor-event synthetic `RivetErrorSchema` values should be `static` when fields are compile-time constants, or process-interned by `(group, code)` with `scc::HashMap` when the default message is dynamic.
- Wasm websocket callback regions should be tracked in a map keyed by active region IDs and removed on end so callback churn does not retain empty slots.
- Wasm websocket callback region ID `0` is the untracked sentinel; `u32` allocation wraps by skipping live IDs instead of panicking.
- NAPI `Ref::unref` needs an `Env`; cleanup from worker or `Drop` paths should be routed through an Env-bearing TSF and must tolerate addon shutdown by falling back to a bounded leak.
- Core `ActorContext::register_task(...)` must race registered runtime promises against `shutdown_deadline_token()` so shutdown drain cannot hang forever.

Started: Sat May  2 02:13:32 AM PDT 2026
---
## 2026-05-02 02:15:18 PDT - US-001
- Implemented wasm `ActorContext.requestSaveAndWait` by awaiting `rivetkit_core::ActorContext::request_save_and_wait`.
- Extended the wasm host smoke harness so `saveState({ immediate: true })` cannot resolve until the host save gate completes.
- Files changed: `rivetkit-typescript/packages/rivetkit-wasm/src/lib.rs`, `rivetkit-typescript/packages/rivetkit/tests/wasm-host-smoke.test.ts`, `scripts/ralph/prd.json`, `scripts/ralph/progress.txt`.
- Checks: `pnpm --filter rivetkit exec vitest run tests/wasm-host-smoke.test.ts`, `pnpm --filter @rivetkit/rivetkit-wasm run check:wasm`, `pnpm --filter rivetkit run check-types`, `pnpm --filter @rivetkit/rivetkit-wasm run check-types`, `pnpm --filter rivetkit exec biome check tests/wasm-host-smoke.test.ts`.
- Full `pnpm --filter rivetkit run lint` currently fails on pre-existing diagnostics in `fixtures/driver-test-suite/*`.
- **Learnings for future iterations:**
  - `ctx.saveState(...)` routes through `WasmCoreRuntime.actorRequestSaveAndWait`, so host smoke tests can model durability waits by blocking fake `requestSaveAndWait`.
  - The wasm crate check may emit existing `rivetkit-core` warnings while still passing.
---
## 2026-05-02 02:24:00 PDT - US-002
- Implemented process-global interning for wasm bridged `RivetErrorSchema` values keyed by `(group, code, default_message)`.
- Added a wasm crate unit test that decodes the same bridged payload repeatedly, checks schema pointer reuse, and verifies a different default message interns separately.
- Files changed: `rivetkit-typescript/packages/rivetkit-wasm/src/lib.rs`, `rivetkit-typescript/packages/rivetkit-wasm/Cargo.toml`, `Cargo.lock`, `scripts/ralph/prd.json`, `scripts/ralph/progress.txt`.
- Checks: `cargo test -p rivetkit-wasm --target wasm32-unknown-unknown --no-run parse_bridge_rivet_error_reuses_interned_schema`, `pnpm --filter @rivetkit/rivetkit-wasm run check:wasm`, `pnpm --filter @rivetkit/rivetkit-wasm run check-types`.
- Native `cargo test -p rivetkit-wasm` currently fails before this crate compiles because `rivetkit-core`'s `wasm-runtime` feature references `wasm_bindgen_futures` on the host target.
- **Learnings for future iterations:**
  - `rivetkit-wasm` has `autotests = false`, so private Rust leak guards are currently easiest as crate unit tests compiled for `wasm32-unknown-unknown`.
  - The wasm-target test harness can be compiled with `cargo test --target wasm32-unknown-unknown --no-run` even when native host tests cannot run.
---
## 2026-05-02 02:26:54 PDT - US-003
- Implemented wasm `engine_binary_path` rejection with a typed `wasm.invalid_config` error before registry serve/serverless startup reaches core.
- Added wasm crate regression coverage for the typed metadata and the `serve` entrypoint fast-rejection path.
- Files changed: `rivetkit-typescript/packages/rivetkit-wasm/src/lib.rs`, `engine/artifacts/errors/wasm.invalid_config.json`, `rivetkit-typescript/AGENTS.md`, `scripts/ralph/prd.json`, `scripts/ralph/progress.txt`.
- Checks: `cargo test -p rivetkit-wasm --target wasm32-unknown-unknown --no-run engine_binary_path_error_is_typed_with_field_metadata`, `cargo test -p rivetkit-wasm --target wasm32-unknown-unknown --no-run serve_rejects_engine_binary_path_before_core_setup`, `pnpm --filter @rivetkit/rivetkit-wasm run check:wasm`, `pnpm --filter @rivetkit/rivetkit-wasm run check-types`.
- **Learnings for future iterations:**
  - Wasm runtime config errors should use typed RivetError values and cross the JS boundary through `anyhow_to_js_error`.
  - Validate wasm-only serve constraints before setting inspector overrides or moving the registry out of `Registering`.
---
## 2026-05-02 02:31:24 PDT - US-004
- Implemented core `ActorContext::register_task(...)` for native and wasm cfgs, backed by the existing shutdown task counter and a distinct `registered_task` user-task metric.
- Updated wasm `ActorContext.registerTask` to call core `register_task` instead of forwarding to `waitUntil`.
- Extended the wasm host smoke harness so a registered task remains pending when shutdown starts and shutdown does not resolve until that task finishes.
- Files changed: `rivetkit-rust/packages/rivetkit-core/src/actor/context.rs`, `rivetkit-rust/packages/rivetkit-core/src/actor/task_types.rs`, `rivetkit-rust/packages/rivetkit-core/AGENTS.md`, `rivetkit-typescript/packages/rivetkit-wasm/src/lib.rs`, `rivetkit-typescript/packages/rivetkit/tests/wasm-host-smoke.test.ts`, `rivetkit-typescript/AGENTS.md`, `scripts/ralph/prd.json`, `scripts/ralph/progress.txt`.
- Checks: `pnpm --filter rivetkit exec vitest run tests/wasm-host-smoke.test.ts`, `pnpm --filter @rivetkit/rivetkit-wasm run check:wasm`, `pnpm --filter @rivetkit/rivetkit-wasm run check-types`, `pnpm --filter rivetkit run check-types`, `pnpm --filter rivetkit exec biome check tests/wasm-host-smoke.test.ts`, `cargo check -p rivetkit-core`, `cargo test -p rivetkit-wasm --target wasm32-unknown-unknown --no-run`.
- **Learnings for future iterations:**
  - `ActorContextHandleAdapter.internalKeepAwake(...)` routes to runtime `actorRegisterTask`, so wasm host smoke tests can exercise it by adding `registerTask(...)` and shutdown-drain behavior to the fake wasm context.
  - Core `register_task(...)` should use shutdown task tracking directly while keeping a separate `UserTaskKind::RegisteredTask` label from public `wait_until(...)`.
---
## 2026-05-02 02:37:23 PDT - US-005
- Moved bridged RivetError HTTP status promotion into `rivetkit-core::error::public_error_status_code`.
- Updated NAPI and wasm bridge encoders to include promoted `public` and `statusCode` values from core, and removed the TS native/wasm adapter promotion tables.
- Removed the decoder-side TS fallback promotion so bridge payload status now comes from the encoded core payload.
- Added NAPI and wasm bridge tests for `auth.forbidden` producing statusCode 403.
- Files changed: `rivetkit-rust/packages/rivetkit-core/src/error.rs`, `rivetkit-rust/packages/rivetkit-core/AGENTS.md`, `rivetkit-typescript/packages/rivetkit-napi/src/lib.rs`, `rivetkit-typescript/packages/rivetkit-napi/tests/actor_factory.rs`, `rivetkit-typescript/packages/rivetkit-wasm/src/lib.rs`, `rivetkit-typescript/packages/rivetkit/src/actor/errors.ts`, `rivetkit-typescript/packages/rivetkit/src/registry/native.ts`, `rivetkit-typescript/packages/rivetkit/src/registry/wasm-runtime.ts`, `rivetkit-typescript/AGENTS.md`, `scripts/ralph/prd.json`, `scripts/ralph/progress.txt`.
- Checks: `cargo check -p rivetkit-core`, `cargo check -p rivetkit-napi --tests`, `cargo test -p rivetkit-wasm --target wasm32-unknown-unknown --no-run wasm_bridge_payload_promotes_known_core_error_status`, `pnpm --filter rivetkit exec vitest run tests/rivet-error.test.ts`, `pnpm --filter rivetkit exec biome check src/actor/errors.ts src/registry/native.ts src/registry/wasm-runtime.ts`, `pnpm --filter rivetkit run check-types`, `pnpm --filter @rivetkit/rivetkit-wasm run check-types`, `pnpm --filter @rivetkit/rivetkit-wasm run check:wasm`.
- Direct `cargo test -p rivetkit-napi napi_bridge_payload_promotes_known_core_error_status` currently fails at the existing standalone N-API linker step with unresolved `napi_*` symbols; `cargo check -p rivetkit-napi --tests` compiles the added test.
- **Learnings for future iterations:**
  - Test-only `#[derive(RivetError)]` structs can generate `engine/artifacts/errors/*.json`; use static `RivetErrorSchema` values in private bridge tests when no artifact should be committed.
  - Keep bridge status promotion at the Rust encoding boundary so old/private/default 500 bridge contexts are normalized before TypeScript decodes them.
---
## 2026-05-02 02:40:27 PDT - US-006
- Added pinned `wasm-pack@0.14.0` as a dev dependency of `@rivetkit/rivetkit-wasm`.
- Updated the wasm build script to resolve and execute the local `wasm-pack` binary through Node instead of using `npx -y`.
- Files changed: `rivetkit-typescript/packages/rivetkit-wasm/package.json`, `rivetkit-typescript/packages/rivetkit-wasm/scripts/build.mjs`, `pnpm-lock.yaml`, `rivetkit-typescript/AGENTS.md`, `scripts/ralph/prd.json`, `scripts/ralph/progress.txt`.
- Checks: `pnpm --filter @rivetkit/rivetkit-wasm build`, `pnpm install --lockfile-only --frozen-lockfile`, `pnpm --filter @rivetkit/rivetkit-wasm run check:package`, `pnpm --filter @rivetkit/rivetkit-wasm run check-types`, `pnpm --filter @rivetkit/rivetkit-wasm run check:wasm`, `node --check rivetkit-typescript/packages/rivetkit-wasm/scripts/build.mjs`.
- Biome ignored the touched wasm package files by repository configuration, so no Biome lint signal was available for this package path.
- **Learnings for future iterations:**
  - The `wasm-pack` npm package exposes a `wasm-pack` bin through `run.js`; resolve it from `wasm-pack/package.json` and execute it with `process.execPath`.
  - Keep the wasm package build offline-friendly after dependencies are installed. Missing `wasm-pack` should fail with an explicit install/dependency error, not a registry fetch.
---
## 2026-05-02 02:46:37 PDT - US-007
- Added `runtime-parity.test.ts` to run the same save, registered-task drain, and promoted bridge-error status scenarios through `NapiCoreRuntime` and `WasmCoreRuntime`.
- Used fake NAPI/wasm binding classes plus `buildNativeFactory(...)` so the parity coverage does not require generated wasm package artifacts.
- Files changed: `rivetkit-typescript/packages/rivetkit/tests/runtime-parity.test.ts`, `rivetkit-typescript/AGENTS.md`, `scripts/ralph/prd.json`, `scripts/ralph/progress.txt`.
- Checks: `pnpm --filter rivetkit exec vitest run tests/runtime-parity.test.ts`, `pnpm --filter rivetkit exec biome check tests/runtime-parity.test.ts`, `pnpm --filter rivetkit run check-types`.
- **Learnings for future iterations:**
  - `ActorContextHandleAdapter.saveState({ immediate: true })` is a useful shared path for parity tests because it exercises NAPI direct calls and wasm `callHandleAsync` while preserving bridged `RivetError` status fields.
  - Fake runtime bindings should keep byte payloads as `Uint8Array`/`Buffer` at the adapter boundary and let `NapiCoreRuntime`/`WasmCoreRuntime` normalize them.
---
## 2026-05-02 02:50:11 PDT - US-008
- Replaced the per-call `action_not_found` schema leak with a compile-time static schema.
- Interned unknown structured timeout schemas by `(group, code)` so dynamic default messages are leaked at most once per unique timeout error.
- Added NAPI moved tests that compare schema pointers across repeated `action_not_found` and unknown structured timeout calls.
- Files changed: `rivetkit-typescript/packages/rivetkit-napi/src/napi_actor_events.rs`, `rivetkit-typescript/packages/rivetkit-napi/tests/napi_actor_events.rs`, `scripts/ralph/prd.json`, `scripts/ralph/progress.txt`, `CLAUDE.md`.
- Checks: `cargo check -p rivetkit-napi --tests`, `cargo check -p rivetkit-napi`.
- Direct `cargo test -p rivetkit-napi action_not_found_reuses_static_schema --no-run` still fails at the existing standalone N-API linker step with unresolved `napi_*` symbols.
- **Learnings for future iterations:**
  - The moved NAPI actor event tests have private-module access through the source-owned `#[path = "../tests/napi_actor_events.rs"]` shim, so pointer-level helpers can test private schema constructors directly.
  - `cargo check -p rivetkit-napi --tests` is the practical compile gate for these Rust tests until the standalone N-API test linker setup is fixed.
---
## 2026-05-02 02:53:12 PDT - US-009
- Replaced wasm websocket callback region tracking with a `HashMap<u32, WebSocketCallbackRegion>` plus a shared monotonic region ID counter.
- Updated `endWebsocketCallback` to remove completed regions instead of leaving `None` slots behind.
- Added a wasm crate regression test that churns callback begin/end calls and asserts the underlying map is empty afterward.
- Files changed: `rivetkit-typescript/packages/rivetkit-wasm/src/lib.rs`, `rivetkit-typescript/CLAUDE.md`, `scripts/ralph/prd.json`, `scripts/ralph/progress.txt`.
- Checks: `cargo test -p rivetkit-wasm --target wasm32-unknown-unknown --no-run websocket_callback_regions_are_removed_after_end`, `pnpm --filter @rivetkit/rivetkit-wasm run check:wasm`, `pnpm --filter @rivetkit/rivetkit-wasm run check-types`.
- **Learnings for future iterations:**
  - Wasm `ActorContext` clones must share both active websocket callback regions and the next region ID so interleaved callbacks remain uniquely tracked across cloned handles.
  - `endWebsocketCallback` should drop the stored `WebSocketCallbackRegion` by removing the map entry; that releases the core guard and keeps memory bounded under repeated callbacks.
---
## 2026-05-02 02:55:33 PDT - US-010
- Investigated the `ActorContextShared::runtime_state` `mem::forget` fallback in NAPI.
- Documented the Env-bearing cleanup design using a cleanup `ThreadsafeFunction`, plus the shutdown edge cases that require preserving a bounded leak fallback.
- Files changed: `docs-internal/engine/napi-bridge.md`, `scripts/ralph/prd.json`, `scripts/ralph/progress.txt`.
- Checks: `jq empty scripts/ralph/prd.json`, `cargo check -p rivetkit-napi`.
- `cargo check -p rivetkit-napi` still emits existing warnings from `rivetkit-sqlite` and `rivetkit-core`.
- **Learnings for future iterations:**
  - `napi::Ref::unref(env)` cannot be called from receive-loop worker reset paths or arbitrary `Drop` paths because both can lack a valid `Env`.
  - A future implementation should wrap stale refs in a TSF payload that forgets the reference on failed delivery, then unrefs only inside the TSF callback while an `Env` is in scope.
---
## 2026-05-02 03:21:47 PDT - US-011
- Changed wasm bridged `RivetErrorSchema` interning to key by `(group, code)` only, reusing the first schema default message even when later payload messages vary.
- Extended the wasm crate cache test so same `(group, code)` with a different live message reuses the schema pointer and does not grow the cache, while a different code still allocates one new schema.
- Files changed: `rivetkit-typescript/packages/rivetkit-wasm/src/lib.rs`, `rivetkit-typescript/CLAUDE.md`, `scripts/ralph/prd.json`, `scripts/ralph/progress.txt`.
- Checks: `cargo test -p rivetkit-wasm --target wasm32-unknown-unknown --no-run parse_bridge_rivet_error_reuses_interned_schema`, `pnpm --filter @rivetkit/rivetkit-wasm run check:package`, `pnpm --filter @rivetkit/rivetkit-wasm run check:wasm`, `pnpm --filter @rivetkit/rivetkit-wasm run check-types`.
- Plain `cargo test -p rivetkit-wasm` still fails on the pre-existing host-target `rivetkit-core` `wasm_bindgen_futures` dependency issue recorded in earlier progress.
- **Learnings for future iterations:**
  - Use unique `(group, code)` namespaces when asserting `BRIDGE_RIVET_ERROR_SCHEMAS.len()` deltas because the intern map is process-global.
  - Schema defaults are only the first-seen fallback for a `(group, code)`; callers should assert live bridged messages through `RivetTransportError.message()`.
---
## 2026-05-02 03:30:40 PDT - US-012
- Implemented bounded shutdown drain for core `ActorContext::register_task(...)` by racing registered futures against `shutdown_deadline_token()` in one shared native/wasm helper.
- Added a core sleep regression test that registers a never-completing future, cancels the shutdown deadline, verifies the shutdown counter drains, and checks the warning includes `actor_id` plus `reason = "shutdown_deadline_elapsed"`.
- Files changed: `rivetkit-rust/packages/rivetkit-core/src/actor/context.rs`, `rivetkit-rust/packages/rivetkit-core/tests/sleep.rs`, `rivetkit-rust/packages/rivetkit-core/AGENTS.md`, `scripts/ralph/prd.json`, `scripts/ralph/progress.txt`.
- Checks: `cargo test -p rivetkit-core register_task_exits_when_shutdown_deadline_cancels`, `cargo check -p rivetkit-core`, `scripts/cargo/check-rivetkit-core-wasm.sh`, `cargo test -p rivetkit-core actor_task_logs_lifecycle_dispatch_and_actor_event_flow`.
- Full `cargo test -p rivetkit-core` was stopped after it hung in the existing `save_tick_cancels_pending_inspector_deadline_and_broadcasts_overlay` test; that test also hung when run in isolation.
- **Learnings for future iterations:**
  - Keep native and wasm `register_task(...)` behavior in a shared helper where possible so the two cfg-specific public signatures cannot diverge.
  - The package-wide core test suite currently has an unrelated hanging inspector debounce test, so use focused tests plus `check-rivetkit-core-wasm.sh` for this shutdown path.
---
## 2026-05-02 03:33:22 PDT - US-013
- Implemented wraparound allocation for wasm websocket callback region IDs, skipping ID `0` and any IDs still live in the active region map instead of panicking at `u32::MAX`.
- Added a wasm-target regression test that seeds the allocator near `u32::MAX`, keeps a low ID live, and verifies wraparound does not collide.
- Files changed: `rivetkit-typescript/packages/rivetkit-wasm/src/lib.rs`, `rivetkit-typescript/CLAUDE.md`, `scripts/ralph/prd.json`, `scripts/ralph/progress.txt`.
- Checks: `cargo test -p rivetkit-wasm --target wasm32-unknown-unknown --no-run websocket_callback_region_ids_wrap_without_collision`, `pnpm --filter @rivetkit/rivetkit-wasm run check:wasm`, `pnpm --filter @rivetkit/rivetkit-wasm run check-types`, `pnpm --filter @rivetkit/rivetkit-wasm run check:package`.
- **Learnings for future iterations:**
  - Wasm websocket callback region allocation preserves the existing `u32` JS surface and uses `0` as the untracked sentinel for the impossible exhausted-ID case.
  - Tests that assert wraparound should keep an existing low region ID live so the allocator proves it skips active IDs after wrapping.
---
## 2026-05-02 03:34:47 PDT - US-014
- Documented that `parse_bridge_rivet_error_reuses_interned_schema` owns the `(wasm_schema_cache_test, same_payload)` cache key so global schema-count delta assertions remain stable under concurrent tests.
- Files changed: `rivetkit-typescript/packages/rivetkit-wasm/src/lib.rs`, `scripts/ralph/prd.json`, `scripts/ralph/progress.txt`.
- Checks: `cargo test -p rivetkit-wasm --target wasm32-unknown-unknown --no-run parse_bridge_rivet_error_reuses_interned_schema`, `pnpm --filter @rivetkit/rivetkit-wasm run check:package`, `pnpm --filter @rivetkit/rivetkit-wasm run check-types`, `pnpm --filter @rivetkit/rivetkit-wasm run check:wasm`.
- **Learnings for future iterations:**
  - Existing Codebase Patterns already cover unique `(group, code)` namespaces for global wasm schema-cache length assertions.
---
