--- id: SMH-RESEARCH-TBKDJHVX type: research title: "Plugin API Standard Library Lessons" --- # Plugin API Standard Library Lessons Grounding for the `smith:plugin` world: deep lessons from respected standard libraries, adapted to a wasm component guest surface. Facts and conclusions are candidates, not mandates. ## Lua — mechanisms, not policies Facts: - Entire stdlib is roughly twenty tables; the language plus libraries implement in the order of tens of thousands of lines. - Design motto from the authors: provide mechanisms, not policies; libraries stay embeddable and host-agnostic in intent. - `io` mirrors C `stdio` (`FILE*` handles, `read` with format verbs); `string.format` mirrors `printf`; `os` exposes `execute`, `getenv`, `date`. - Sources: [Lua 5.4 manual](https://www.lua.org/manual/5.4/), [The Evolution of Lua (HOPL)](https://www.lua.org/doc/hopl.pdf). Extract: - Mechanisms-not-policies is the deepest fit: the host exposes typed primitives, plugins compose policies. No world interface should encode a workflow. - A total surface teachable in one sitting. Budget: every world interface should fit on one page. Reject: - The C-mirror: printf verbs, stdio metaphors, 1-based indexing. Guest APIs must speak component-model vocabulary (records, resources, results), never host-history vocabulary. - Ambient OS reach (`os.*`): already impossible under the sandbox; keep it impossible in world design, not just engine config. ## Java — contracts, cruft, and the cost of forever Facts: - `java.util.Date`: mutable, timezone-hostile, mostly deprecated for decades with no removal path. `Stack extends Vector`, `Properties extends Hashtable`: inheritance as API surface locking in implementation detail. - `Enumeration`→`Iterator`, `Observer`/`Observable` removed in 9, `Optional` arriving decades after null: corrections layered onto unremovable mistakes. - Counterexample inside the same stdlib: the Collections Framework — interfaces (`List`, `Map`, `Set`) separated from implementations (`ArrayList`, `HashMap`), each with documented complexity contracts. It stayed good because the seam was interfaces, not classes. - Source: [Java SE API](https://docs.oracle.com/en/java/javase/21/docs/api/java.base/module-summary.html). Extract: - Interface/implementation separation at the collection level is the same seam as plugin module contracts: publish the contract, swap the implementation. - Documented behavioral contracts (complexity, thread-safety, nullity) as first-class API surface. Reject: - Inheritance as extension mechanism: world guests get composition only. - Mutable default types: every world record is a value; mutation belongs to resource handles with explicit methods. - The forever-deprecated zombie layer: deprecations must carry a replacement and a removal path from day one (already WASM0001 world-versioning law). ## Go — one-method contracts, one grammar Facts: - `io.Reader` and `io.Writer` are single-method interfaces; nearly all byte flow in the ecosystem composes through them. - Proverb canonized by the designers: the bigger the interface, the weaker the abstraction. - `context.Context` threaded through every cancellable call after 1.7: cancellation became an explicit parameter, retrofit cost enormous. - Inhomogeneity is real: errors as values here, booleans there; per-package conventions drift despite `godoc` uniformity. - Sources: [io package](https://pkg.go.dev/io), [Go proverbs](https://go-proverbs.github.io/). Extract: - Single-purpose contracts: prefer several one-function WIT interfaces over one wide one; guests implement what they use. - Cancellation as an explicit parameter of every long-running world function — matches our budget and abort laws. - Uniform documentation grammar across the entire world: same section order, same terminology, machine-checkable. Reject: - Implicit interface satisfaction (not expressible in WIT regardless). - Convention drift between packages: one naming and error grammar for the whole world, reviewed mechanically. ## Zig — explicit allocation, compositional performance Facts: - `std.io.Reader`/`Writer` compose through duck-typed vtable structs; the 2024 IO redesign generalizes composition further and is still settling — the library is young and messy by its own admission. - Allocation is an explicit parameter everywhere; nothing allocates behind your back; buffers are passed, not grown implicitly. - Error sets are explicit unions in signatures; performance is a stated design constraint of the API shape itself. - Source: [Zig io guide](https://zig.guide/standard-library/io). Extract: - No hidden allocation or copying: every world function that moves bytes takes explicit bounds and buffer parameters. - Error sets as precise enumerated failure vocabularies — resist collapsing failures into one `error` case. - Performance belongs to the contract: batched calls, explicit budgets (our fuel and invocation budget are the same doctrine). Reject: - Duck typing via `anytype`: unavailable in WIT; composability must come from shared record types. - The churn of a young redesign: pin the world early, evolve additively, never mid-stream reshape. ## Elm — naming discipline, mechanical semver Facts: - Core is around ten modules; the whole API fits in a practitioner's head. Argument order conventions exist and hold: data first, function last (`List.foldl fn acc list`). - The style guide bans abbreviations and presumes nothing: names say what they mean, consistently, across the entire ecosystem. - `elm/package` enforced semantic versioning mechanically: publishing computed the API diff and derived the version bump — breaking changes could not masquerade as patches. - `Json.Decode` makes untrusted payload parsing an explicit typed pipeline; nothing decodes implicitly. - Sources: [elm/core](https://package.elm-lang.org/packages/elm/core/latest/), [style guide](https://elm-lang.org/docs/style-guide). Extract: - Data-first parameter ordering for every world function; the convention is free until violated once. - No abbreviations; one glossary for the whole world, enforced in review. - Mechanical semver from API diff is directly transplantable: `smith plugin check` should diff worlds and classify additive versus breaking, feeding the world-version law instead of trusting authors. - Explicit typed decoding of untrusted provider payloads — already our provider law; the world should expose decode helpers, not implicit conversions. Reject: - Purity guarantees: paradigm does not transfer; reachability of effects transfers instead (capability grants). ## Rust — layering, prefix grammars, checklist discipline Facts: - `core`/`std` split: the always-available substrate versus the fuller environment layered above. - Naming grammar is codified: `as_` borrowed conversions, `to_` expensive copies, `into_` consuming conversions; `#[must_use]` marks effects ignoring-is-a-bug. - The [API guidelines](https://rust-lang.github.io/api-guidelines/) are a mechanical review checklist: casing tables, doc examples that compile and run, no panics in public API, no `unwrap` in shipped code. - Coherence rules keep impls from colliding ecosystem-wide. - Source: [std docs](https://doc.rust-lang.org/std/), [API guidelines](https://rust-lang.github.io/api-guidelines/). Extract: - Two-layer world: a core world every plugin sees, plus granted capability interfaces layered on top — the exact analogue of `core`/`std`, and it matches WASM0001 imports-with-grants law. - Prefix grammar for world function names; adopt one and hold it. - A written API-guidelines checklist for the smith world, applied to every world change, with compiling doc examples in the SDK. Reject: - Allocator assumptions baked into API shapes: guests own linear memory; world signatures express ownership by structure (values in, values out, handles by resource). ## OCaml — signatures as the ecosystem unit Facts: - `.mli` interface files make the signature a first-class authored artifact; implementations seal against them explicitly. - Functors parameterize modules over modules; the ecosystem (opam) publishes packages whose value is the interface, with competing implementations ( MirageOS protocol providers are the canonical family ). - Source: [OCaml modules manual](https://ocaml.org/manual/modules.html). Extract: - Interface-first authoring: a plugin's manifest and world exports are authored like an `.mli` — the contract is the product, the implementation is replaceable. - This is already SPEC0001 law (module contracts, zero/one/many implementations, major-version binding); the research reinforces it: the binding model is the ecosystem's load-bearing wall, not a convenience. ## Wasm boundary limitations Verified constraints and design consequences for the world: | limitation | consequence | mitigation | |---|---|---| | no generic signatures | contracts name concrete types | SDK-layer generics deferred past 1.0; world stays concrete | | no closures or function values | callbacks impossible | named imports; host pulls chunks and re-invokes renewal | | no trait objects | no boundary polymorphism | resources with methods are the polymorphism unit; module contracts are resources | | values cross by copy | chatty APIs cost | batched calls, explicit buffers | | no shared memory | no host–guest aliasing | generation law; copies only | | fuel cannot interrupt host calls | guest fuel ≠ host timeout | host effects carry own deadlines and cancellation | | 32-bit memories | 4 GiB per instance | ceilings law | | traps poison the instance | guest panic unrecoverable | typed results for ordinary failures; generation reload for traps | | `result` only | no exception propagation | precise variant error sets | | no variadics or defaults | rigid signatures | none needed if world stays small; SDK optional later | | no structural subtyping | version tolerance must be designed | additive-only worlds plus unknown catch-all | | guest threads experimental | no intra-guest parallelism | host-orchestrated concurrency law | | async types stabilizing | unstable to depend on | explicit pull model; never await guest futures | | component-to-component linking complicates ownership | plugin-to-plugin design choice | route through host effects; keep grants, fuel, generations coherent | | instantiation cost | per-call spin-up is real | instantiate at registration; pool instances | Rust guests keep full generics internally through monomorphization; only signatures crossing the boundary are concrete. Bindgen maps WIT types to idiomatic guest types per language; unknown future data travels as a tagged `list` inside the catch-all variant. SDK stance: no smith-authored SDK before 1.0; the world must be complete and comfortable through raw bindgen guests alone, and an SDK stays optional forever after. ## Synthesis: rules for the smith:plugin world 1. Minimal total surface; every interface needs two real consumers before entering the world (Zig, Lua). 2. One naming grammar, one error grammar, one documentation shape across all interfaces (Go, Elm). 3. Tiny universal contracts over wide interfaces; several one-function interfaces beat one fat one (Go, Zig). 4. Explicit over implicit: buffers, bounds, cancellation, provenance are parameters, never ambient state (Zig, Go). 5. Vocabulary types with impossible-to-misuse constructors; precise enumerated failure sets (Rust, Zig). 6. Additive-only evolution; every deprecation carries a replacement and removal path (Java, WASM0001). 7. Performance is part of the contract: batched calls, explicit budgets (Zig, our fuel law). 8. Two-layer world: core plus granted capability interfaces (Rust `core`/`std`). 9. Data-first parameter order; no abbreviations; one glossary (Elm). 10. World version bumps are computed from API diffs, not asserted by authors (Elm mechanical semver). 11. Interfaces are the ecosystem unit: contracts publish, implementations swap, binding is explicit (OCaml, Java Collections). 12. Mechanisms, not policies: no world interface encodes a workflow (Lua). 13. World-first: the WIT world is the only API; no smith-authored SDK ships before 1.0, and an SDK stays optional forever — comfort through raw bindgen is a design requirement. 14. One world major for the host API (`smith:plugin@N`); plugin-published contracts carry their own majors; versioning every interface separately recreates classpath chaos for no benefit. 15. Deprecation is metadata with a named replacement and a removal major; enforcement is mechanical at `smith plugin check`, never a breaking change by itself. New APIs enter `@unstable`, exempt from append-only until stabilized; a world major ships with zero unstable items. 16. Flat interfaces inside one world; the fixed `namespace:package/interface` hierarchy is all the structure a world needs. 17. Trust flows through the distribution channel — embedded and release-signed, or installed and hash-recorded — never through runtime privilege. ## Sources - Lua 5.4 reference manual — - The Evolution of Lua — - Java SE API — - Go io package — - Go proverbs — - Zig io guide — - Elm core and style guide — , - Rust std and API guidelines — , - OCaml modules — - Component model and WIT — ## Unresolved questions - Do Reader/Writer-style byte contracts belong in the world, or only inside HTTP and stream interfaces where chunks already flow? - Which failure vocabulary granularity survives WIT result variants before becoming a Java-style exception taxonomy? - How do plugin-published module contracts compose with world versioning: are they worlds of their own, or interface resources inside the smith world? - Can `smith plugin check` diff worlds mechanically today (wasm-tools), or does the tooling gap force author-asserted versions initially? - How does a consumer detect additive interface features within one major — unknown catch-alls cover data, not functions? - Which signing scheme fits remote implementation updates, and does it ride the release signature or a separate update key?