feat(routing-policy): native-Go preset engine so the TinyGo wasm-visor can run presets (no wazero) - #4040
Merged
Conversation
…r can run presets (no wazero) The built-in routing-policy presets (rotating-bw, latency-adaptive, elastic-mux, probe-and-prune, adaptive, app-mux, geo-avoid, transport-diverse, trust-tiered, time-of-day) could only run on the NATIVE visor: "preset:<name>" loads the embedded bundle.wasm and evaluates it via wazero. A wasm-visor is itself TinyGo wasm and cannot host wazero (wasm-in-wasm), so it never evaluated presets — its router was built with a nil DialHook. Extract the preset decide/tick logic into ONE pure-Go package, pkg/router/policy/preset (stdlib-only, no unsafe/JSON-ABI/wazero/starlark), as the single source of truth, and compile it into BOTH paths: - docs/examples/routing-policies/wasm/bundle/main.go becomes a thin wasm ABI shim over the preset package (JSON wire types + alloc/free/decide_route/on_tick glue only); bundle.wasm rebuilt from it. The native visor still runs this bundle via wazero, byte-identically. - pkg/router/policy/presethook adapts the preset package to the router.DialHook / RouteSelectingHook / RotationHook interfaces WITHOUT wazero, TinyGo-safe (imports only pkg/router + preset). cmd/wasm-visor wires it as the router's DialHook, selected by the ?routing_policy=<name> query param (unset ⇒ nil hook ⇒ unchanged no-policy behavior). Byte-identical guarantee (gate-1) extended to the native path: a new parity_test drives the SAME inputs through the wazero bundle and the native preset package and asserts identical decisions — field-for-field on decide for every preset, and step-for-step over a multi-tick on_tick sequence for the stateful controllers (EWMA smoothing, AIMD peak/idle, probe state machine). The existing wazero end-to-end presets_test and the bundle's native main_test still pass unchanged. Verified: go build . (root), go test ./pkg/router/policy/..., GOOS=js GOARCH=wasm build of ./cmd/wasm-visor, and a full TinyGo-fork build of ./cmd/wasm-visor (the committed embed lane) all pass. Router-integration note: the conditional presets (geo-avoid, transport-diverse) read per-hop geo/transport-kind metadata; on the wasm-visor these come from a presethook.Provider, which is nil today (NopProvider), so those two defer until a wasm-side metadata provider is wired — a follow-up. trust-tiered and all the shape/tick presets work with no provider.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Problem
The built-in routing-policy presets only ran on the native visor.
preset:<name>loads the embeddedpkg/router/policy/wasm/presets/bundle.wasmand evaluates it via wazero. A wasm-visor is itself TinyGo wasm and cannot host wazero (wasm-in-wasm), so it never evaluated presets —cmd/wasm-visorbuilt its router with a nilDialHook, andpkg/router/policy(+.../wasm) don't compile under TinyGo (starlark / wazero).Approach
Extract the preset decide/tick logic into one pure-Go package,
pkg/router/policy/preset(stdlib-only: nounsafe, no JSON ABI, no wazero, no starlark) as the single source of truth, and compile it into both paths:docs/examples/routing-policies/wasm/bundle/main.gois now a thin wasm ABI shim over that package — it keeps only the JSON wire types and thealloc/free/decide_route/on_ticklinear-memory glue, delegating every decision topreset.bundle.wasmis rebuilt from it (551798 → 551439 bytes). The native visor keeps running this bundle via wazero, unchanged.pkg/router/policy/presethookadapts thepresetpackage to therouter.DialHook/RouteSelectingHook/RotationHookinterfaces without wazero — TinyGo-safe (imports onlypkg/router+preset).cmd/wasm-visorwires it as the router'sDialHook, selected by the?routing_policy=<name>query param. Unset ⇒ nil hook ⇒ unchanged (no-policy) behavior; an unknown name is rejected (no silent fallback).pkg/router/policy/hook.goare untouched — the native selection path (pkg/visor/policy_loader.go→ wazero) is unchanged.Byte-identical guarantee (gate-1, extended to the native path)
A new
parity_test.godrives the same inputs through the wazero bundle and the nativepresetpackage and asserts identical results:The existing wazero end-to-end
presets_test.goand the bundle's nativemain_test.gostill pass unchanged.Verified
go build .(repo root)go test ./pkg/router/policy/...(incl. wazero end-to-end + new parity + new unit tests)GOOS=js GOARCH=wasm go build ./cmd/wasm-visor./cmd/wasm-visor(the committed embed lane) — the preset engine compiles into the browser visor blob.Follow-up (documented gap)
The wasm-visor now invokes the preset engine on every dial via the
DialHook. The conditional presetsgeo-avoid/transport-diverseread per-hop geo / transport-kind metadata, supplied on native by apolicy.Provider; on the wasm-visor this comes from apresethook.Provider, which is nil today (NopProvider), so those two defer until a wasm-side metadata provider is wired.trust-tiered(needs only hop PKs) and all the shape/tick presets work with no provider.