Language and runtime matrix
Status: WS12.1 deliverable. Section 1 is the definitive language/runtime matrix. Section 2 is the per-file port/dedupe plan that WS12.2 executes. All source paths verified against the live trees on 2026-07-02, then adversarially fact-checked (two independent review passes); citations below are to the 0.8.5 SDK tree unless marked otherwise.
Source trees referenced:
- Harness under port:
/Users/tj/software/OrbPro/packages/orbpro-integration/sdk/src/runtime/(10 files, 1789 lines) - Port target (canonical SDK):
/Users/tj/software/orbpro-stack/repos/main-packages/OrbPro/packages/space-data-module-sdk(v0.8.5, HEAD0d53c54) - Warning:
/Users/tj/software/OrbPro/packages/space-data-module-sdkis a stale v0.8.4 copy (nosrc/flow/, no worker harness, no SAB channel, no timer driver). Do not port into it. sdn-js pins the SDK as a tarball directly at commit53748bc92bdac04a2372fe4a6dafa0e4a0abd43d(sdn-js/package.json:100) — one commit behind HEAD0d53c54(the workspace ROOTspace-data-network/package.json:84carries a separate, older pin atcfc9b538…) (the commit that addedsrc/flow/and the"./flow"export). The frozen-compat surface for sdn-js is therefore the pinned commit's exports, which do not include./flow.
1. Language/runtime matrix
Framing: hosts embed a runtime to execute SDN .wasm modules; guests are the languages modules are written in. All SDN modules are standalone-WASI wasm compiled from C/C++ only (emscripten em++/emcc selected in selectCompiler, space-data-module-sdk/src/compiler/compileModule.js:78-84; STANDALONE_WASM=1 at compileModule.js:130). No Rust, AssemblyScript, TinyGo, or Python guests exist anywhere in the codebase.
| Host environment / language | Module runtime used | WasmEdge native? | Status in this codebase | Evidence |
|---|---|---|---|---|
Go node (sdn-server) — Go | WasmEdge embedded in-process via CGO (second-state/WasmEdge-go v0.14.0) | YES (in-process, production) | In use — the production module runtime | sdn-server/go.mod:27; internal/wasmrt/runtime.go:12,117-130 (wasmedge.NewVMWithConfig, THREADS/EXCEPTION_HANDLING/WASI); internal/modulert/module.go:167-175 |
| Browser — JS/TS (sdn-js UI, OrbPro) | Plain WebAssembly.instantiate + pure-JS WASI shim + the space_data_module_host hostcall bridge (the JS harness) | NO — WasmEdge does not exist in browsers | In use — createBrowserModuleHarness, createWorkerModuleHarness, createFlowRuntimeHost | sdn-js/src/ui/runtime/live-delivery.ts:2,204; SDK src/host/wasiShim.js:33, src/host/abi.js:10 (DEFAULT_HOSTCALL_IMPORT_MODULE = "space_data_module_host", matching Go internal/modulert/hostbridge.go:28), src/host/browserModuleHarness.js, src/flow/flowRuntimeHost.js:36 |
| Node.js — JS/TS, subprocess path | WasmEdge out-of-process: spawn("wasmedge", ...) CLI, invoke request over stdin/stdout | Yes, but only as an external binary | In use (Node harness default for standalone artifacts) | SDK src/host/isomorphicLoader.js:89,112; src/testing/processInvoke.js:111,131 |
| Node.js — JS/TS, pure-JS path | Plain V8 WebAssembly + the same browser harness (wasiShim + hostcall bridge) | NO | In use (vitest, isomorphic artifacts, no-wasmedge-binary environments) | wasiShim.js:1-12 ("same .wasm that runs in WasmEdge … instantiated directly"); src/host/browserModuleHarness.js |
| C — native test runner | WasmEdge embedded via the C API (wasmedge/wasmedge.h) | YES (in-process, test-only) | In use (pthread module test runner only) | SDK src/testing/native/wasmedge_emscripten_pthread_runner.c:9; src/testing/buildWasmEdgeRunner.js |
| C/C++ — as guest language | n/a (compiles to the wasm modules themselves) | n/a | In use — the only guest language | All module families (propagator/*, foundation/*, comms/*, closed-modules *.cpp); compiler supports exactly .cpp → em++ / else emcc |
| Rust | WasmEdge Rust SDK (wasmedge-sdk) exists upstream | Would be native | N/A — not used (only vendored emsdk test fixtures contain .rs) | Zero Cargo.toml outside deps/emsdk/.../test/rust/ |
| Python | WasmEdge Python SDK exists upstream (experimental) | Would be native | N/A — not used | No Python host or guest anywhere |
| Java | wasmedge-java exists upstream | Would be native | N/A — not used | No Java anywhere |
Node.js napi binding (wasmedge-core) | — | — | Deliberately not used — project shells out to the CLI instead | No napi dep in SDK or sdn-js/package.json |
Definitive conclusion
- Environments that NEED the JS-style harness (WebAssembly.instantiate +
createBrowserWasiShim+ thespace_data_module_hosthostcall bridge): the browser, always (no WasmEdge exists there), and Node.js whenever the isomorphic/browser artifact path is used or nowasmedgebinary is on PATH. This is the entire reason the harness exists and the reason the orbpro-integration runtime mirrors WasmEdge ABI semantics in JS. - Environments that run WasmEdge natively (in-process): Go (CGO bindings — the only production embedding) and C (C API — testing-only pthread runner). Nothing else.
- Out-of-process WasmEdge: Node.js via
spawn("wasmedge")only. - Rust, Python, Java: upstream WasmEdge SDKs exist but are not used and no work in this plan targets them. Any future host in those languages would embed WasmEdge natively and would not need the JS harness.
- The JS harness is therefore not a stopgap; it is the permanent execution tier for the browser and the WasmEdge-mirror reference for Node. That is why WS12.2 moves it into
space-data-module-sdk(the package that already owns the browser/Node execution surface) rather than leaving it in orbpro-integration.
2. Port/dedupe plan — the 10 orbpro-integration/sdk/src/runtime/ files
Conventions for WS12.2:
- Target base = the 0.8.5 SDK at
/Users/tj/software/orbpro-stack/repos/main-packages/OrbPro/packages/space-data-module-sdk. - Generated-code precursor (corrected scope): the harness imports
../generated/orbpro/{flow,manifest,stream,plugin}.js. In the 0.8.5 SDK the manifest namespace is already generated (src/generated/orbpro/manifest/plugin-manifest.js,PMANidentifier at lines 30/248). What is missing is (a) the flow namespace entirely and (b)StreamInvokeRequest/StreamInvokeResponsein the plugin namespace (src/generated/orbpro/plugin.jsexports onlyRawDataPayload). Regenerate both in the SDK tree as the WS12.2 precursor — do not import across packages. Note the SDK already hand-rolls StreamInvoke flatbuffer classes atsrc/testing/streamInvokeCodec.js(classes at lines 13/67/96): once the generated plugin namespace lands,flowCodec.js(#2) andstreamInvokeCodec.jsmust converge on ONE implementation (the generated one), withstreamInvokeCodec.jsreduced to a re-export or deleted. - The
sdn-flowgithub pin: the harness'ssdk/package.json:83pin is carried bycompiledFlowHost.js:1andMethodRegistry.js:1(plustests/CompiledFlowHostSpec.js:4). See #3/#6 — the pin cannot be fully dropped in WS12.2 (production consumers of the bound-host API remain in orbpro-integration); the port removes the SDK's need for it and leaves a scoped follow-on.
| # | File (lines) | Verdict | Target / action | ||
|---|---|---|---|---|---|
| 1 | bufferLike.js (28) | DROP | Byte-identical twin already exists at SDK src/runtime/bufferLike.js (same three exports: isArrayBufferLike, hasByteAddressableBuffer, toUint8Array). Rewrite the two internal consumers (codec, streamBridge) to import from ../runtime/bufferLike.js. Zero-risk deletion. | ||
| 2 | codec.js (94) | PORT (split, with renames) | The StreamInvokeRequest/Response + FlowProgram ("FLOW") codecs are a distinct envelope from the SDK's existing PluginInvokeRequest/Response codec (src/invoke/codec.js) — do not conflate them. Port encode/decodeFlowProgram and encode/decodeStreamInvoke{Request,Response} to new SDK file src/flow/flowCodec.js (subsuming src/testing/streamInvokeCodec.js, see precursor). The manifest codec does NOT merge: the SDK's encodePluginManifest (src/manifest/codec.js:258) is encodePlgManifest(legacyManifestToPlg(manifest)) — it emits a $PLG-identified buffer (src/manifest/plgCodec.js:37), consumed by compileModule.js:830,889 and SDK embeddedManifest.js:32 — while the harness's codec emits the PMAN-identified PluginManifest buffer that orbpro-integration's sdk/src/embeddedManifest.js:5 depends on. These are different wire formats; both must exist. Port the PMAN pair with renamed exports encodePluginManifestPman/decodePluginManifestPman (avoids star-export collision with the PLG-flavored names in src/manifest/index.js:7/browser.js:7 re-exported via src/browser.js:1), living in src/flow/flowCodec.js beside the flow codecs. Internal toByteBuffer moves with it. Browser-safe; only dep is flatbuffers (already an SDK dep). | ||
| 3 | compiledFlowHost.js (35) | MERGE (option) + KEEP compat shim | createFlowRuntimeHost (0.8.5 src/flow/flowRuntimeHost.js) already runs the compiled flow runtime.wasm with Go-parity ABI. Merge INTO it the one new behavior here: withLegacyHostImportCompat — the stub import module sdn_flow_host with dispatch_current_invocation: () => 0 (caller entries win) so legacy artifacts instantiate under plain WebAssembly.instantiate — as an instantiate option (legacyHostImportCompat: true or an extraImports hook). bindCompiledFlowRuntimeHost is NOT deleted: it has production consumers — orbpro-integration/default-suite.js:631,738 call createDefaultPluginCompiledFlowHost (defaultRuntimeRegistry.js:164-166 → bindOrbProCompiledFlowHost), passing sdn-flow bound-host options and consuming the bound-host API (host.runEntrypoint, host.dispatchNextReadyNodeWithHost, host.drainWithHost…, default-suite.js:668-700) that the SDK host does not provide. The wrapper (and its sdn-flow/host import) stays in orbpro-integration as a compat adapter; migrating default-suite off the bound-host API onto the SDK flow host is a follow-on task, after which the sdk-package sdn-flow pin can go. | ||
| 4 | FlowRuntime.js (275) | PORT to src/flow/jsFlowRuntime.js | New capability: a pure-JS FlowProgram interpreter (no runtime.wasm needed) — the fallback/test-tier scheduler, complementing flowRuntimeHost.js which requires the compiled artifact. Port intact but align contracts with the wasm host: same backpressure policies (queue/drop-oldest/drop-newest, depth ≥1 or Infinity), same yield semantics (response.yielded === true breaks the drain), same default maxInvocationsPerDrain = 1024, and make drain()'s return shape ({invocations, idle, queues}) documented alongside flowRuntimeHost.drain(). It must invoke through the registry surface chosen in #6. Export as space-data-module-sdk/flow alongside createFlowRuntimeHost. Browser-safe; only sibling import is the normalizer (#8). | ||
| 5 | index.js (35) | DROP | The barrel is orbpro-integration packaging, not code. Its 18 names (8 codec fns + bindOrbProCompiledFlowHost + createDependencyStreamBridge + MethodRegistry + FlowRuntime + 5 sdnCompat exports + generateLegacySdnShimSource) get re-homed: flow pieces + flow/PMAN codecs → ./flow, registry → ./runtime-host, compat → new ./compat (see #7), shim generator → ./compiler (see #9); bindOrbProCompiledFlowHost stays in orbpro-integration (#3). The renamed PMAN exports (#2) prevent barrel collisions with the SDK's existing PLG-flavored encodePluginManifest/decodePluginManifest. Update the SDK package.json exports map and src/browser.js accordingly. orbpro-integration keeps a thin re-export barrel for back-compat (Section 3). | ||
| 6 | MethodRegistry.js (40) | VENDOR sdn-flow's registry; wrapper stays thin | The file itself is a 40-line wrapper, but the sdn-flow/runtime contract behind it is not small: at the pinned commit (4dd28e2, verified against the local baseline worktree) sdn-flow/src/runtime/MethodRegistry.js is 400 lines + runtime/normalize.js 506 lines, enforcing duplicate-pluginId and missing-per-method-handler rejection (:231/:245 — defaultRuntimeRegistry.js:93-120 relies on these throws for its registration-failed reporting), required-input-port presence (:330), min/max distinct-stream bounds (:339-347), acceptedTypeSets frame-type rejection (:350-357), undeclared-input-port rejection (:362-367), internal-transport aligned-binary typeRef coercion with capability/interface exception sets (:10-28,:140-172), outputStreamCap overflow errors (:176-179), single-output portId defaulting (:181-188), and drainPolicy defaulting (:370-373). Reimplementing "lookup + call + normalization" would silently drop validation the ported specs assert. Plan: vendor sdn-flow's MethodRegistry.js + runtime/normalize.js into the SDK (src/runtime-host/methodRegistry.js + supporting normalize), preserving behavior verbatim (no github pin — vendored source), integrate with createModuleRegistry at the seams (manifest-aware registerPlugin accepting PluginManifestT \ | flatbuffer bytes \ | plain object via #8's normalizer), and keep the class-shaped MethodRegistry export. The five orbpro-integration spec files are the parity gate. |
| 7 | sdnCompat.js (449) | PORT to new src/compat/sdnLegacy.js (new ./compat subpath) | No SDK equivalent exists for the legacy SDN metadata JSON envelope (buildLegacySdnMetadata — the exact snake_case JSON that plugin_get_metadata returns from a WasmEdge-loaded module) or the cron/protocol dispatch adapters (SdnCompatAdapter.invokeCron/invokeProtocol with legacyBridge context tagging, timer resolution by timerId-or-methodId, default-port stamping). This is the JS twin of the Go host's legacy surface and is consumed by the shim generator (#9). Port whole. Rewrites: generated imports (manifest, stream namespaces) → SDK generated tree; decodePluginManifest import → decodePluginManifestPman (#2); registry type → #6. Browser-safe (TextEncoder only). Do not change any field name, enum casing (UPPER_SNAKE), or the BigInt "<n>ms" interval formatting — this JSON is a wire contract with the Go host. | ||
| 8 | sdnFlowAdapter.js (276) | PORT (internal) to src/flow/normalize.js | Required by #4 and #6: normalizeManifestForSdnFlow / normalizeProgramForSdnFlow encode the ABI conventions (numeric flatbuffer enums → kebab-case strings, maxBatch: 1 / drain-until-yield / queue/depth-1 defaults, schemaHash typed-array → plain array). Keep it internal — not in the exports map, exactly as it is today (never re-exported by the harness barrel). Since #6 vendors sdn-flow's registry (the sdn-flow reference stays meaningful), keep the exported names as-is. Rewrites: generated imports + decodePluginManifestPman/decodeFlowProgram from their new SDK homes. Browser-safe. | ||
| 9 | sdnShimGenerator.js (323) | PORT to src/compiler/sdnShimGenerator.js | The SDK-surface audit's confirmed gap: no hostcall/legacy-ABI codegen exists in the SDK (only build-side compiler/invokeGlue.js). This generates the C shim exporting the legacy WasmEdge-host ABI (plugin_get_metadata / plugin_handle_request / plugin_cron, caller-allocated buffers, int32 length-or-negative-error). Port whole into the compiler layer, next to its natural consumer (the build pipeline; orbpro-integration's sdnArtifacts.js writes its output as sdn-compat-shim.c). The C template must be moved byte-identically: error constants -1..-5, EMSCRIPTEN_KEEPALIVE exports, \xNN string escaping, memcmp method dispatch, and the single-protocol-method constraint of resolveRequestMethodId are all ABI/behavior contracts. Rewrites: generated manifest import, decodePluginManifestPman + buildLegacySdnMetadata from their new SDK homes (#2/#7). The file is browser-safe (pure string generation) even though it lives under the Node-leaning ./compiler subpath — note that in the module docstring so a future browser build tool can import it. | ||
| 10 | streamBridge.js (234) | PORT to src/flow/dependencyStreamBridge.js | Fills the other confirmed gap: a flow host that can invoke dependency wasm modules itself (guest malloc/free arena staging, TypedArenaBufferT input framing with defaults alignment=8/BORROWED/IMMUTABLE, invokeRawStream with the StreamInvokeRequest envelope, copy-out of (offset,size) outputs via cloneBytes so bytes survive memory growth, guaranteed release() in finally, deduped output-pointer freeing). flowRuntimeHost.drain() currently delegates non-linked-direct nodes to caller-supplied JS handlers; this bridge is what turns a dependency descriptor's instantiated module into such a handler. Port whole; wire an optional integration in flowRuntimeHost so a caller can pass {instantiatedDependency} per dependencyId instead of a raw handler. Merge notes: import toUint8Array/hasByteAddressableBuffer from SDK src/runtime/bufferLike.js (#1), the StreamInvoke codecs from src/flow/flowCodec.js (#2), and converge its output-frame shape with src/invoke/codec.js's TypedArenaBuffer materialization where field names differ (traceToken vs traceId — keep both readable, emit the harness's names for continuity). Do not replace it with the browser harness's direct surface: that speaks the PluginInvokeRequest envelope to plugin_invoke_stream; this speaks StreamInvokeRequest to invokeRawStream. They are different guest ABIs and both must exist. |
Summary of verdicts
- DROP (2):
bufferLike.js(identical twin in SDK),index.js(packaging only; names re-homed on SDK subpaths, PMAN codecs renamed). - MERGE (1):
compiledFlowHost.js→ itswithLegacyHostImportCompatoption merges intosrc/flow/flowRuntimeHost.js; thebindCompiledFlowRuntimeHostbound-host adapter remains in orbpro-integration (production consumers indefault-suite.js). - VENDOR (1):
MethodRegistry.js— vendor sdn-flow's full registry + normalize intosrc/runtime-host/(validation semantics preserved verbatim). - PORT (6):
codec.js→src/flow/flowCodec.js(flow + StreamInvoke codecs; PMAN manifest codec as renamedencode/decodePluginManifestPman),FlowRuntime.js→src/flow/jsFlowRuntime.js,sdnCompat.js→src/compat/sdnLegacy.js,sdnFlowAdapter.js→src/flow/normalize.js(internal),sdnShimGenerator.js→src/compiler/sdnShimGenerator.js,streamBridge.js→src/flow/dependencyStreamBridge.js. - Net effect: the SDK gains no new external dependencies (only
flatbuffers, already present; sdn-flow arrives vendored, not pinned). Thesdn-flowgithub pin at orbpro-integrationsdk/package.json:83can be dropped only after the follow-on default-suite migration (#3); the rootorbpro-integration/package.json:94pin and thedefault-suite.js:99-100lazysdn-flow/auth+sdn-flow/deployimports are out of scope for WS12.2 entirely.
3. Risks and constraints for WS12.2
- Port into 0.8.5, not 0.8.4. The copy at
/Users/tj/software/OrbPro/packages/space-data-module-sdkis stale (v0.8.4, missingsrc/flow/,workerModuleHarness,sabHostcallChannel.js,hostcallWire.js,timerDriver.js). All merges above name 0.8.5-only files. Decide early whether the stale OrbPro copy gets synced or retired; do not let the two copies diverge further. - Do not disturb the existing SDK export surface. sdn-js pins the SDK tarball directly at
53748bc92bdac04a2372fe4a6dafa0e4a0abd43d(sdn-js/package.json:100; v0.8.5, one commit before./flowexisted) and imports these subpaths: root,/compiler,/host/timer-driver,/licensing,/runtime-host,/testing,/testing/browser,/testing/module-flatbuffer-stream-pump,/transport. It does not import./flow. Additions to the exports map are fine; renames/removals of existing subpaths orsrc/browser.jsre-exports are not. - orbpro-integration consumers must keep working. Three surfaces depend on the harness today: (a) the public subpath
@orbpro/integration-sdk/runtime(sdk/package.json:18) and the 18 names re-exported bysdk/index.js:61-80/sdk/node.js; (b) deep imports inorbpro-integration/defaultRuntimeRegistry.js:1-2,12(FlowRuntime.js,MethodRegistry.js, lazycompiledFlowHost.js); (c) in-SDK consumerssdk/src/sdnArtifacts.js:3-6andsdk/src/embeddedManifest.js:5(PMAN bytes — see #2). WS12.2 must leave compat shims at the old file paths (one-line re-exports fromspace-data-module-sdk) until OrbPro-side callers are migrated — especially the deep-imported files, which the barrel cannot cover. - Two invoke envelopes coexist; neither may change.
PluginInvokeRequest/Response(SDKsrc/invoke/codec.js, used byplugin_invoke_stream/_startsurfaces, the worker harness, and the Go host) vsStreamInvokeRequest/Response(flow dependencyinvokeRawStreamABI). Keep them in separate files with distinct names; a unification refactor is out of scope and would break compiled artifacts. - Two manifest envelopes coexist; neither may change. The SDK's
encodePluginManifest/decodePluginManifestare$PLG-flavored (src/manifest/codec.js:258viaplgCodec.js); the harness's arePMAN-flavored. The ported PMAN pair ships under the renamedencodePluginManifestPman/decodePluginManifestPmanexports; consumers of each keep their exact bytes. - Wire/ABI contracts that must stay byte-identical: the
"PMAN"and"FLOW"flatbuffer file identifiers (and the fact that StreamInvoke envelopes have no identifier); the legacy metadata JSON field set/casing/"<n>ms"BigInt intervals (buildLegacySdnMetadata); the generated C shim template including error codes −1…−5 and export names; thesdn_flow_hostimport-module name anddispatch_current_invocationstub in the compat option; TypedArenaBuffer staging defaults (alignment 8, BORROWED, IMMUTABLE, generation 0); thespace_data_module_hosthostcall import-module name (src/host/abi.js:10= Gohostbridge.go:28). - Vendoring sdn-flow's registry — test parity is the gate. The vendored
MethodRegistry+normalize(#6) must behave identically to the pin; the five orbpro-integration spec files (UnifiedRuntimeSpec,SdnCompatSpec,CompiledFlowHostSpec,StreamInvokeBridgeSpec,SdnShimGeneratorSpec) must be ported alongside the code and pass against the SDK build before the shims land.CompiledFlowHostSpecimports layout constants fromsdn-flow/hostdirectly — rewrite those assertions againstflowRuntimeHost.js's own struct-layout constants (60/72/48/24/32/24-byte descriptors). - Browser-graph hygiene. Everything ported is browser-safe (verified: zero
node:imports across the 10 files). Keep it that way:src/compat/sdnLegacy.jsandsrc/flow/*go intosrc/browser.js; the shim generator goes only under./compiler(Node-leaning) even though it is technically browser-safe; nothing ported may importnodeHost.js,sdnArtifacts.js-stylefs/path, or the subprocess loaders. - Don't re-port what 0.8.5 already finished: worker/SAB execution (
workerModuleHarness+sabHostcallChannel+hostcallWire), cron/timer re-invocation (timerDriver+cron.js), isomorphic load/inspect (isomorphicLoader,detectArtifactProfile), and the compiled-flow wasm host's descriptor decoding (flowRuntimeHost). The port adds the JS interpreter tier, the dependency stream bridge, legacy compat, and the shim generator — nothing else. - Naming collision watch: the SDK already exports
createModuleRegistry; the vendoredMethodRegistrymust be documented as the flow-tier registry (validating, sdn-flow-derived) vscreateModuleRegistry(module lifecycle) to avoid a third registry concept. SimilarlyjsFlowRuntimevscreateFlowRuntimeHostneed one doc page distinguishing "interpreter (no runtime.wasm)" from "compiled-artifact host (Go parity)".