● Phase D — Worker glue + deploy ergonomics ✅ #29
Loading…
Add table
Add a link
Reference in a new issue
No description provided.
Delete branch "wasm-tier-2-phase-D"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
D1g — emit overrideable glue (crates/compiler/src/worker_glue.rs, new)
→ write query → plg_rt_run_query → decode the (len<<32)|ptr BigInt → free). Query from
?query= or POST body; 400 on missing query; per-request limit knobs exposed.
file to refresh it). The CLI reports what it wrote vs. preserved, with deploy/serve hints.
build failure. Unit tests cover name sanitization and the write-then-preserve behavior.
D2g — just wasm-worker-serve <prog.pl> (justfile)
config.capnp. Productizes the spike's serve loop.
Verification — proven on real workerd (the actual Workers runtime, V8)
Installed workerd locally and ran the emitted config.capnp:
error(existence_error(procedure, /(nope, 1)), ...)"} matched the native binary exactly ✅
That closes the Tier-2 Definition-of-Done gate: "--target worker → a .wasm (+ glue) that
answers HTTP on workerd byte-identical to native, including errors." Combined with
reactor-smoke's automated 1,000,000-deep-recursion-on-V8 proof, the two load-bearing DoD
items are both met and repeatable.
Full workspace green; fmt + clippy clean (default and --features wasm). Stray build artifacts
cleaned up; workerd lives in /tmp only.
D1g — emit overrideable glue (crates/compiler/src/worker_glue.rs, new) - plgc build --target worker prog.pl now drops three files next to the reactor .wasm: - worker.js — a Cloudflare/workerd fetch handler driving the buffer ABI (init once → alloc → write query → plg_rt_run_query → decode the (len<<32)|ptr BigInt → free). Query from ?query= or POST body; 400 on missing query; per-request limit knobs exposed. - wrangler.toml — Cloudflare deploy config (CompiledWasm rule, sanitized worker name). - config.capnp — local workerd serve config. - Write-if-absent: a rebuild regenerates the .wasm but never clobbers edited glue (delete a file to refresh it). The CLI reports what it wrote vs. preserved, with deploy/serve hints. - Wired at the CLI layer (main.rs Build handler, per C3) — a glue failure is a warning, not a build failure. Unit tests cover name sanitization and the write-then-preserve behavior. D2g — just wasm-worker-serve <prog.pl> (justfile) - Compiles to target/worker/<stem>/ (emitting glue there) and exec workerd serve config.capnp. Productizes the spike's serve loop. Verification — proven on real workerd (the actual Workers runtime, V8) Installed workerd locally and ran the emitted config.capnp: - GET ?query=needs(app, X) → 5 solutions ✅ - POST body depends_on(app, D) → 2 solutions ✅ - Missing query → 400 ✅ - Error path byte-identical to native over HTTP: nope(x) → {"error":"Runtime error: error(existence_error(procedure, /(nope, 1)), ...)"} matched the native binary exactly ✅ That closes the Tier-2 Definition-of-Done gate: "--target worker → a .wasm (+ glue) that answers HTTP on workerd byte-identical to native, including errors." Combined with reactor-smoke's automated 1,000,000-deep-recursion-on-V8 proof, the two load-bearing DoD items are both met and repeatable. Full workspace green; fmt + clippy clean (default and --features wasm). Stray build artifacts cleaned up; workerd lives in /tmp only.Review — #29 Phase D: Worker glue + deploy ergonomics
The scaffolding model is right and the glue is well-built. Write-if-absent is the correct ergonomic call — the
.wasmis the real artifact (always regenerated) and the three glue files are editable scaffolding (never clobbered), with the CLI reporting wrote-vs-preserved and a glue failure correctly demoted to a warning since the build already succeeded. The unit tests pin both the sanitizer and the write-then-preserve behavior (including the edited-file case). A few things in the generatedworker.jsI specifically checked and liked:ex.memory.bufferafterplg_rt_run_query. The resultUint8Arrayis created after the call returns, so a memory growth during solving that detaches the oldArrayBuffercan't produce a stale view. This is the classic wasm-host footgun and it's avoided.runQueryis fully synchronous. The onlyawaitisrequest.text()for the POST body, before the ABI dance; alloc→run→free never yields. So the reactor's single-in-flight contract (D3) holds automatically even when workerd runs multiplefetchinvocations concurrently in one isolate — they serialize on the event loop. That's the subtle correctness property and it's correct.Layoutalloc/free contract from Phase A), and the i64step_limitis passed as aBigInt. 400 on missing/empty query, query-param precedence over POST body.Real concern
1. The deployable
worker.jsis a third, untested copy of the buffer ABI.reactor-smoke— the automated test added per the #28 review — drivesscripts/reactor-smoke.mjs, which states in its own header: "This is a TEST driver, not the deployable worker glue (that is productized in Phase D)." So the marshalling sequence (plg_init→alloc→run_query(ptr,len,limit,steps,depth)→ decode(len<<32)|ptr→free) now lives in three places that must move in lockstep:plg_rt_run_query's 5-arg signature),scripts/reactor-smoke.mjs(tested byjust reactor-smoke),WORKER_JStemplate (this PR — tested by nothing).The artifact users actually deploy is #3, and its only validation is the one-time manual
workerd serverun in the PR description. The per-request limit args are precisely the kind of signature change that already happened once (#27 grewrun_queryfrom 2 args to 5); the next such change has to be mirrored in all three, and CI guards only one of them. A silent drift inworker.jsships green and breaks at deploy.Concretely, any one of these closes it:
reactor-smokeat the emittedworker.jsinstead of a parallel.mjs— e.g. a thin Node shim that imports the generatedrunQuery/fetchand round-trips the same queries. Then the deployable glue is the thing under test and the.mjscan go away (one fewer copy).worker.jsand the smoke driver import, so there's one copy to drift.WORKER_JScontains the current 5-argplg_rt_run_query(qptr, bytes.length, limit, BigInt(stepLimit), depthLimit)call, so a reactor-ABI change that isn't mirrored in the template failscargo test.I'd want at least the cheap option before merge — it's the same gap shape as #28 (the tested thing isn't the shipped thing), one level up.
Small observations
2.
worker_name's second strip recomputes its own receiver.The closure recomputes an expression identical to the value
.strip_suffix(".worker")was called on, so it can only ever return that same value.let stem = wasm_file.strip_suffix(".wasm").unwrap_or(wasm_file); stem.strip_suffix(".worker").unwrap_or(stem)is equivalent and reads as what it means. Correct as-is, just needlessly clever.3. GET query isn't trimmed but POST is.
?query=%20%20is truthy → forwarded to the reactor as a parse error, while the same goal via POST body is.trim()'d to empty → 400. Harmless (worst case is a parse-error JSON), just an asymmetry; trimming the param too would make them consistent.4.
worker_namedoesn't collapse runs of hyphens or cap length.my__app→my--app, and a long stem passes through whole. Cloudflare is generally lenient on internal double-hyphens and the input is a short filename stem, so this is low-stakes — noting it only because the function's whole job is "produce a Cloudflare-legal name."Net: #1 is the one worth addressing — everything about the glue content looks correct, but the deployable copy has no automated guard against ABI drift, and the ABI has already proven it drifts. The rest are polish.