Alex Dunmow c35199f0bc feat(wasmguest): guest runtime shim for the wasm plugin ABI (WO-WZ-002)
The wasip1 half of the ABI: bn_alloc/bn_invoke/bn_free exports with a
live-pin map so the GC never frees host-visible buffers, the generic
`blockninja.host_call` import (single import decided over per-family
symbols; recorded in wasm-abi.md), and a dispatch table adapting an
unmodified plugin.PluginRegistration to all nine v1 hooks. Panics inside
plugin hooks come back as ABI_ERROR_CODE_INTERNAL — the instance stays
callable; traps stay reserved for runtime corruption.

DESCRIBE builds the PluginManifest from the registration's static funcs
plus a capture-only Register pass (block metas via the same
PluginBlockRegistry prefixing the .so loader applies, template/system/
page-template/email-wrapper keys), probes JobHandlers/ServiceHandlers/
Load with capture-only services for job types, RBAC roles, core-service
bindings, and RAG fetcher types. RenderContext values are rehydrated
through the exact core/blocks context keys, so existing block code
reading from ctx works unchanged.

Plugins build in REACTOR mode (go build -buildmode=c-shared): init()
calls wasmguest.Serve (non-blocking), main is never called, and the host
runs _initialize before any bn_invoke. Command mode deadlocks or exits
(verified against wazero v1.12.0) — documented prominently in
wasm-abi.md, which also now reconciles the import module namespace to
`blockninja` and requires bn_alloc'd buffers on both directions.

Dispatch/describe/context logic is buildable on every GOOS; only
exports.go and hostcalls.go carry the wasip1 tag. dispatch_test.go
covers describe, hook routing, envelope mismatch, decode failures,
template-override resolution, panic recovery, and lifecycle hooks
natively.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-03 13:53:24 +08:00

47 lines
1.9 KiB
Go

// Package wasmguest is the guest half of the BlockNinja wasm plugin ABI:
// the go:wasmexport entry points (bn_alloc / bn_invoke / bn_free), guest
// memory pinning, the generic host-call import, and the dispatch table that
// adapts an unmodified plugin.PluginRegistration to ABI hook calls.
//
// The wire contract lives in core/docs/wasm-abi.md; the messages in
// git.dev.alexdunmow.com/block/core/abi/v1.
//
// # Plugin boilerplate
//
// A plugin becomes a wasm plugin by adding one main file next to its
// Registration:
//
// //go:build wasip1
//
// package main
//
// import "git.dev.alexdunmow.com/block/core/plugin/wasmguest"
//
// func init() { wasmguest.Serve(Registration) }
// func main() {} // never called — reactor mode
//
// and compiling in REACTOR mode (Go >= 1.24 toolchain; this repo's floor is
// higher):
//
// GOOS=wasip1 GOARCH=wasm go build -buildmode=c-shared -o plugin.wasm .
//
// Reactor modules export `_initialize` instead of `_start`: the host runs
// `_initialize` once per instance — which runs the init func above, hence
// Serve — before any bn_invoke. Serve is non-blocking: it stores the
// registration, runs the Register pass against capture registries, and
// returns; all work then arrives through bn_invoke.
//
// Command mode (plain `go build`) does NOT work and must not be used: its
// `_start` runs main synchronously, so a blocking main deadlocks the Go
// runtime ("all goroutines are asleep") and a returning main exits and
// closes the module — either way the exports are never callable
// (empirically verified against wazero v1.12.0; see wasm-abi.md).
//
// # Build shape
//
// Dispatch, describe, capture-registry, and context-reconstruction logic is
// plain Go and builds on every GOOS so it stays natively testable. Only
// exports.go (go:wasmexport, memory pinning) and hostcalls.go
// (go:wasmimport blockninja host_call) carry the wasip1 build tag.
package wasmguest