🔬 Spike: run Cloudflare Worker Shell and Worker JavaScript natively in Deno (#351) - #353
Conversation
PR #353: 🔬 Spike: run Cloudflare Worker Shell and Worker JavaScript natively in Deno (#351)23 files, +4163 / -4 Scope🔴 PR has 4167 lines changed. Split into focused PRs. 🟡 4167 lines changed. PRs under 400 receive more thorough review. 🟡 23 files changed. Are all changes related? 🟡 PR mixes config and source changes. 🟡 New abstraction files: spikes/351-worker-backends/vendor/worker-shell/adapter.ts. Verify 3+ consumers. Structural🟡 4 console statements. Slop
Static Analysis✅ Oxlint found no issues. CorrectnessNo extraneous code patterns detected. |
|
CI scope note: this PR targets The four verification gates were run locally on this branch instead, all clean:
Plus the spike's own suite: Full CI will run once #350 merges and this retargets to |
#351) Both of Cloudflare Computer's imperative backends execute from one compiled Deno artifact against the #349 workspace, with no workerd, Wrangler, Docker, or second JavaScript runtime. The shell reuses Cloudflare's filesystem adapter byte-identical and drops the Dynamic Worker transport, since the workspace is in-process. The JavaScript backend is a port of WorkspaceRuntimeLoader: an in-memory module graph supplied as blob URLs with rewritten specifiers, run in a Deno Worker, with the workspace capability crossing as async postMessage RPC and nothing materialized to disk. Nine scenarios cover bidirectional coherence across restarts, workspace identity isolation, shell semantics, module graphs with dependencies, committed-state coherence, and explicit refusal of operations the host does not install.
) Worker Shell is include-initially-scoped: it runs natively over the Deno-local workspace, reuses Cloudflare's adapter byte-identical, and its containment was measured — around fifty escape attempts with zero leaks and no native-execution path. Its one hazard is availability, not capability: a CPU-bound script starves the host event loop, so the boundary recommendation is a Deno Worker. Worker JavaScript is defer: the whole contract works, including an in-memory module graph with no materialization, but isolation holds only in a compiled artifact and CPU-bound user code cannot be preempted. The missing Deno primitive is named precisely. COMPARISON.md answers the two topology questions separately and states that bundled workerd is not necessary for this scope.
d546046 to
702861b
Compare
| // Declared structurally so tests can swap in a fake or a real stub | ||
| // without coupling the adapter to the cloudflare:workers RpcTarget | ||
| // base class. Production callers pass `workspace.stub().fs`; the | ||
| // shape matches that stub one-for-one. |
There was a problem hiding this comment.
Redundant comment — restates what the code does.
| // shape matches that stub one-for-one. |
| // The store doesn't have a native rename today, so model mv as | ||
| // copy+delete. POSIX mv is atomic when src and dest live on | ||
| // the same filesystem; this approach isn't, but it matches | ||
| // what just-bash's other adapters do. |
There was a problem hiding this comment.
Redundant comment — restates what the code does.
| // what just-bash's other adapters do. |
| // through" — for a non-symlink the answer is the canonical | ||
| // path; for a symlink the answer is the readlink target | ||
| // resolved relative to its parent. Stat the path first so a | ||
| // missing entry surfaces ENOENT. |
There was a problem hiding this comment.
Redundant comment — restates what the code does.
| // missing entry surfaces ENOENT. |
Why
#351 asks whether Cloudflare Computer's Worker Shell and Worker JavaScript can run over the Deno-local DOFS Workspace (#349) without
workerd, Wrangler, Docker, or another JavaScript runtime — so #346 can pick the smallest local topology that still supplies usefulAPI.Processbehavior. Both are optional imperative extensions over the declarative durable core, not prerequisites for it.Measured verdicts
Bundled
workerdis not necessary for this scope — both execution models run natively in Deno at 139.7 MB carrying both backends plus DOFS, versus 191 MB for the workerd host.What changes
Before: #349 proved the Deno-local filesystem but had no imperative execution over it; the only proven backends were #347's, inside
workerd.After:
deno task spike:351builds one compiled artifact that runs both backends against the #349 workspace, with 9 scenario tests covering coherence, isolation, semantics, and refusal behavior.How it works
WorkspaceFsAdaptervendored byte-identical +just-bash. TheWorkerEntrypoint/Workers-RPC/Dynamic-Worker glue exists only to reach a filesystem across an isolate boundary, so it disappears when the workspace is in-process. New code: a ~60-line shim for the three stub-only methods.WorkspaceRuntimeLoader— in-memory module graph as blob URLs with specifiers rewritten to absolute blob URLs (relative imports don't resolve from a blob base), run in a Deno Worker, filesystem capability crossing as async postMessage RPC. Nothing is materialized to disk.Key findings (full detail in evidence/EVIDENCE.md)
/etc/passwd,../../..traversal,find /,grep -r /all resolve inside DOFS; environment is fabricated; no native execution exists (/bin/sh,id,uname→ command not found); QuickJS guest has noDeno, blockedFunctionconstructor, workspace-scopedrequire("fs"); network off by default, with allowlist + private/loopback rejection + DNS-rebinding pinning when enabled.permissions: "none"governs Deno ops, not the module loader — underdeno runa locked worker still importsjsr:/npm:;deno compile's frozen graph closes it. Adeno runhost must never be presented as a sandbox.Worker.terminate()cannot preempt CPU-spinning JS. Needed Deno-side: a CPU/wall budget onnew Worker(...), or an interruptingterminate(). Cloudflare'slimits.cpuMshas no equivalent.@cloudflare/dofs; a redirect to a missing parent throws out ofbash.exec();ls -lreports a stale mode.What must stay true
spikesis root-excluded, the spike owns its deps, no root dependency changed. All four run clean at root.just-bashis an ordinary npm dependency (Apache-2.0, Vercel Labs — not Cloudflare).<Workspace>, provider, process API, or declarative components — evidence only.How to verify it
deno task spike:351from the repo root./bin/*invocation ever runs.evidence/probes/(every command and escape attempt).Scope
Included
spikes/351-worker-backends/, root task aliases.Intentionally unchanged
<Dir>,<File>,<Branch>,<Commit>) — the spike confirms the substrate exposes what they need without implementing them.spike/issue-349-dofs-adapters) since it consumes that spike's vendored DOFS; the rootdeno.jsontask edits overlap textually with 🔬 Spike: bundle workerd into the compiled XMD host (#347) #348/🔬 Spike: host Cloudflare DOFS directly in Deno with SQLite (#349) #350.Risks and limitations
python3inside just-bash remains unusable under Deno (off by default);js-exec/sqlite3need a one-lineprocess.connectedprelude.