Expand description
In-process SSR dispatch for mode:"ssr" routes (R449-F2; supersedes the
bun-subprocess implementation R434-F3 shipped).
On startup the manifest is read; if any route is mode:"ssr" an
mesofact_ssr::SsrRuntime is booted in this process. Each route’s
render_entrypoint is pre-loaded into the isolate (paid once at startup),
keyed by its derived URL prefix. The dev server then calls
SsrChild::dispatch for each matching request — one V8 turn, no
cross-process hop, no HTTP serialisation.
The bun subprocess + ssr-wrapper.ts + reverse-proxy machinery is gone. So
is the requirement for bun on PATH; the dev server now works on a
plain Rust toolchain.
@yah:relay(R444, “Plumb dev S3 coords into in-process SSR isolate so R2Adapter resolves at runtime in dev hotreload”)
@yah:status(review)
@yah:assignee(agent:bundle-anthropic-miravel)
@yah:at(2026-08-13T19:29:07Z)
@yah:next(“Thread the dev S3 coords from mesofact-dev’s main/ssr::spawn into SsrRuntime::start (crates/mesofact-dev/src/ssr.rs:380 + the SpawnOptions struct) and on into the mesofact-ssr isolate bootstrap.”)
@yah:next(“Expose them to JS inside the isolate so @mesofact/runtime config.ts requireEnv(env, …) resolves: either inject a process.env shim (globalThis.process = { env: {…} }) in the bootstrap, or pass an explicit env map the runtime’s registerSourcesFromConfig consumes. Decide which the runtime should read (process.env shim is least-invasive to existing TS).”)
@yah:next(“Verify end-to-end (this completes R490-F7’s PENDING criterion): a mesofact dev app with a [sources.r2] source doing r2.fetch/list inside an SSR render handler resolves against mesofact-dev’s s3s-fs in bun run dev and returns the bytes (PUT one, fetch it back through the rendered route).”)
@yah:next(“Coordinate the env-var-name convention with R490-F7: today mesofact-dev injects conventional R2_* names; keep the isolate shim consistent (or have mesofact-dev read the workload’s mesofact.config.toml to learn the declared endpoint_env names).”)
@yah:gotcha(“Cross-camp seam: this is the runtime-reads half of the PARENT camp’s R490-F7 (in the yah camp at /Users/leif/ss/yah). R490-F7 landed the dev S3 surface (s3s-fs in mesofact-dev) + BUILD-TIME r2 reads via the build subprocess env. The blocker for RUNTIME reads is here in the subcamp: the in-process V8 SSR runtime (SsrRuntime, R449-F2) can’t inherit process.env, so the @mesofact/runtime R2Adapter executing inside SSR render code never sees R2_ENDPOINT.”)
@yah:gotcha(“mesofact-dev already computes the coords (DevS3::env_vars(): R2_ENDPOINT/R2_BUCKET/R2_ACCESS_KEY_ID/R2_SECRET_ACCESS_KEY) and writes .mesofact-dev/s3.json. The missing piece is getting those into the isolate’s JS env so registerSourcesFromConfig() can resolve [sources.r2].”)
@yah:handoff(“Dev S3 coords now reach the in-process SSR isolate end-to-end. Rust: SpawnOptions gained env: Vec<(String,String)> + with_env() (crates/mesofact/src/ssr.rs); mesofact-dev’s main.rs starts DevS3 BEFORE the first ssr::spawn (was after) and threads dev_s3.env_vars() into both the initial spawn and the post-build respawn hook; mesofact/src/cli/serve.rs (prod) passes std::env::vars() since that process genuinely has yubaba-injected secrets.”)
@yah:handoff(“mesofact::ssr::spawn resolves [sources.r2] from the workload’s mesofact.config.toml against opts.env (new resolve_r2_sources, fail-fast on a missing/empty declared env var, same boot-time contract mesofact-worker’s runWorker uses) and calls the new SsrRuntime::register_r2_sources before any route registers.”)
@yah:handoff(“mesofact-ssr: SsrRuntime::start(env) Object.assign’s env onto globalThis.process.env right after the bootstrap script (before harness/any route loads) via a new mesofact-ssr:env execute_script. New Job::RegisterR2 + call_bridge (a call_harness variant with no bundle-URL keying) push resolved R2SourceCoords into the isolate.”)
@yah:handoff(“ssr_runtime_shim.js’s r2()/sqlite() were a HARDCODED THROWING STUB pre-R444 (registerSourcesFromConfig was a no-op) — discovered mid-relay, not just missing env plumbing as the ticket text assumed. Replaced with a real per-isolate registry + a ported R2Adapter (BaseSource noTrack/timeout/tag-emit, S3 GET/LIST-v2, XML parser) using plain unsigned fetch() — works against mesofact-dev’s anonymous s3s-fs (AllowAllAccess) but NOT real Cloudflare R2 (needs SigV4). sqlite() stays a throwing stub (out of R444’s r2-only scope).”)
@yah:handoff(“DISCOVERED + FIXED (blocking, in-blast-radius): DispatchTarget::dispatch (mesofact/src/ssr.rs) called the blocking SsrRuntime::dispatch round-trip INLINE on the caller’s async executor thread (‘isolate round-trip is fast, no I/O’ — no longer true once route code does real fetch()). On a single-threaded runtime that also hosts the I/O the route awaits (mesofact-dev’s own dev-S3 surface in this crate’s tests; any single-worker deployment), that’s a hard deadlock: the one thread able to service the connection is the one now blocked waiting on it. Fixed by Arc
Structs§
- Hook
Entry - One declared Mode 2 hook — schema mirror of
mesofact_core::manifest::Hook. - LogBuffer
- Bounded ring buffer for SSR runtime log lines. Kept around so the dev log surface that expected stderr lines from the bun child still has something to render — though the in-process runtime writes far fewer lines.
- Manifest
- Parsed
manifest.jsonslice. Only the fields the SSR path cares about. - Resilience
Policy - W181 v1 — schema mirror of
mesofact_core::manifest::ResiliencePolicy. - Retry
Policy - Route
Entry - Spawn
Options - Options for
spawn. The workload directory anchors the state dir; the gen_dir is the snapshot the SSR runtime should resolve entrypoints against. - SsrChild
- In-process SSR dispatch + the data the router needs to use it.
- SsrSlot
- Swappable holder for the SSR child. The router reads
current()on every request; the watcher’s post-build hook installs (or rotates) the child after each successful gen flip. Cheap to clone.
Constants§
- DEFAULT_
RESILIENCE_ TIMEOUT_ MS - Default per-attempt request timeout when
resilience.timeout_msis unset.
Functions§
- derive_
prefix - W173 derivation: prefix is everything up to the first
:paramor*segment. Non-parametric SSR routes use the full path. - matches_
prefix - W173 segment-aware match:
path == prefix || path.startsWith(prefix + "/"). - spawn
- Inspect the manifest; if it has any SSR route, boot an SsrRuntime, pre-
load each route’s render_entrypoint, and return a
SsrChildhandle. ReturnsOk(None)when no SSR routes are declared — the caller serves static only.