picatz / picatz/flowstate

Flowstate runs in WASM today: the whole pkg tree compiles under wasip1 and js/wasm (design record + slice plan)

Open
#242 0 comments 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

engine enhancement
Dominant language
Go
Stars
9
Forks
0
Avg merge
3h 3m
Merged PRs (30d)
509

Description

Feasibility pass on running the local driver — the Temporal-free execution path — under WebAssembly. The headline is better than expected and independently verified:

GOOS=wasip1 GOARCH=wasm go build ./pkg/flowstate/v1/ ./pkg/flowstate/v1/flowfile/   # exit 0
GOOS=js     GOARCH=wasm go build ./pkg/flowstate/v1/ ./pkg/flowstate/v1/flowfile/   # exit 0

The whole ./pkg/... tree compiles under both targets today, including pkg/flowstate/v1/plugin/. Every WASM incompatibility in this codebase is a runtime boundary, not a build boundary. That reshapes the effort estimate entirely: slice 1 is not "make it compile," it is "prove it runs, and lock the property so it can't regress."

Why it already works

The local driver's import set is context/errors/fmt/math/reflect/strings/time + cel-go + the expr protos — all pure Go. cel-go pulls no Temporal and no gRPC runtime into the core. And the seams that WASM needs were already injected for other reasons: the clock (NewContextWithClock, VirtualClock), signals (SignalWaiter), the task registry (NewContextWithRegistry), secrets (TaskRuntime), and compile-from-bytes (flowfile.Parse is byte-based and fs-free — only ParseFile and call: resolution touch the filesystem, by design, at the authoring edge). The plugin host already has a proc_unix.go/proc_other.go build-tag seam, so WASM compiles against stubs and simply never wires a host.

Invariant 3 holds by construction: grep for //go:build / runtime.GOOS across the core execution path returns nothing — a WASM build is the existing local driver on a new GOOS, not a third driver. Same RunWithInputsevalrunNodes, byte-for-byte the same logic.

The three targets, disambiguated

(a) Compile / validate / fix in WASM — highest value/effort ratio. Pure and byte-based; compiles today. Unlocks a browser playground, LSP-in-browser, runnable docs. call: degrades to a clean diagnostic (no fs), not a crash.

(b) No-egress local execution in WASM — a real near-term slice. log + CEL transforms + loop/for_each/if/parallel + waits with a VirtualClock (so sleep: 24h is instant) + embedder-registered custom Go tasks. The work is a thin //go:build wasm entrypoint and JS glue — not core surgery, because targets (a) and (b) are essentially "call the embed facade (#230/#232) from a wasm main."

(c) http via browser fetch (js/wasm) — feasible, highest effort, separate PR. Under js/wasm an http.Client whose transport does not override DialContext uses Fetch automatically — and the blocker is precisely that netpolicy.newClient() hardcodes a net.Dialer. Needs an injectable transport seam. Fail-closed is load-bearing here: a WASM build with no egress shim must deny http, not silently no-op it — the absent-transport case returns a deny error, matching the embed facade's own nil-EgressPolicy-means-default-deny posture.

Plugins are categorically out (no subprocess, no unix sockets) — so the WASM-expressible capability set is the trusted base plus embedder-registered tasks, which is exactly the right subset for a demo and for embedding.

Slice 1 (one PR)

  1. A CI build check for GOOS=wasip1 and GOOS=js over the core + embed packages — locks in the "compiles under wasm" property so it cannot silently regress. Highest signal, lowest cost.
  2. A minimal //go:build wasm example main: Flowfile bytes → embed.Compileembed.RunLocal with a VirtualClock and a log-only registry → outputs. Runnable under wasmtime as a CI smoke target, honoring the house rule that a capability lands when an example exercises it in CI.
  3. No transport shim, no plugin host, call:-free — with http either absent from the registry or wired to deny, proving the fail-closed boundary explicitly.

The netpolicy transport seam for target (c) is a deliberate follow-on: it changes a security-sensitive construction path and deserves its own review.

Honest limits (do not oversell)

"Run any workflow in the browser" is not the claim. http needs the transport seam; plugins are impossible; call: needs host-side resolution before the bytes are handed in. What is true is that the compile core and the no-egress execution core already compile under wasm with their seams already injected — the remaining work is packaging and glue.

Contributor guide

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Research direction

Start with the existing pkg/flowstate/v1/ and pkg/flowstate/v1/flowfile/ packages, then inspect the embed Compile and RunLocal entry points plus VirtualClock. Run the stated wasip1 and js/wasm builds before adding the CI check and minimal wasm main. Done means the core and embed packages compile for both targets and a call-free, no-egress example runs under wasmtime in CI.

Written by the indexing model from the issue text.

Assessment

Tech stack
go, wasm
Domain
build-system, testing-qa, web-dev
Issue type
Feature
Difficulty
5/5
Estimated time
Over a week
Activity status
Quiet
Clarity
Mostly clear
Newbie friendliness
48/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.