Expand description
holger’s package-handler plugin contract — the one wire format a handler
speaks whether it is linked into the server or loaded from a .wasm at
startup.
Rickard’s ask was that every package handler should be writable as native or
WASM. Before this crate holger had no wasm path at all: the sixteen
repository backends are unconditional path dependencies of server/lib, so
adding a package handler meant editing server/lib/Cargo.toml and rebuilding
the server.
§The two seams
A RepositoryBackendTrait is a Rust trait object and cannot cross a wasm
boundary, so this crate splits a handler in two along the line that actually
matters:
PackageHandler— the format logic: coordinate parsing, the HTTP path scheme, listing, content types. This is what varies per ecosystem and what a plugin author writes. It is pure and target-independent, so the one implementation compiles for the host and forwasm32-unknown-unknown.BlobStore— the bytes. A wasm module has no filesystem and no socket. The host owns storage and hands it in.
That split is what makes “the wasm crate is ABI glue only” true rather than
aspirational: the native backend implements BlobStore over the filesystem,
the wasm shim implements it over four host imports, and both call the same
PackageHandler. It is the same shape as znippy’s maven pair, where
host-decompressors compiles the threaded ljar fan-out out of the wasm
build and leaves the single-threaded linflate both sides share — same
logic, different plumbing underneath.
§Which verbs cross, and why
RepositoryBackendTrait (traits/src/lib.rs:404) has twelve methods. Six
cross:
| verb | why it is in |
|---|---|
plugin_manifest | the module must name itself, or registration needs a config file and the whole point of scanning a directory is lost |
fetch | the read. Non-defaulted on the trait. |
put | the write. Non-defaulted on the trait. |
list | enumeration; the UI and retention planner both read it |
coordinate_for_path | the serve-time quarantine gate calls it before bytes go out, and it is pure path logic — the cheapest and most format-specific thing a handler owns |
handle_http2_request | non-defaulted, and it is the actual door a package client knocks on. A handler that could not answer it would not be a package handler. |
Six do not, and the host supplies the trait’s own default for each:
| verb | why it is out |
|---|---|
name / format / is_writable | constants. Read once from the manifest at load, not per call. |
archive_files / archive_info / has_archive | these describe a znippy archive handle. A wasm module has none, and the trait already defaults them to “no archive” — which is the truth here, not a stub. |
delete_artifact | the trait default fails closed, and the contract requires the implementer to recompute the stored digest and refuse a mismatch. A sandboxed module cannot be trusted to have done that, and a wrong answer deletes bytes. Failing closed is the correct answer, not a missing feature. |
§ABI shape
Every exported verb takes a (ptr, len) pair into linear memory and returns
a pointer; the byte length of that result is read back with result_len(),
exactly as znippy’s loader does. Payloads are length-prefixed
(codec) — never JSON, whose separators and missing null representation
are a corruption source rather than a parser bug.
Re-exports§
pub use codec::DecodeError;pub use codec::Reader;pub use codec::Writer;pub use wire::WireArtifactEntry;pub use wire::WireArtifactId;pub use wire::WireHttpRequest;pub use wire::WireHttpResponse;pub use wire::WireListRequest;pub use wire::WireManifest;pub use wire::WirePutRequest;pub use wire::ABI_VERSION;
Modules§
- codec
- The byte codec both surfaces share. One writer, one reader — the host links these functions and so does every guest, so a field cannot be encoded one way and decoded another (LAW 5, by construction rather than by a guard watching two copies agree).
- exports
- Exports a
.wasmpackage handler must provide. The host names the missing one when a module falls short, so a half-built module is a loud load failure rather than a backend that answersNoneto everything. - guest
- The guest side of the ABI, written once and generated per module.
- imports
- Host functions a module imports from
envto reach storage. The host owns every byte; the module owns only the format logic. - wire
- The values that cross the boundary, and their encoders.
Macros§
- export_
package_ handler - Generate the eight ABI exports for a
crate::PackageHandler.
Traits§
- Blob
Store - The bytes behind a handler. Implemented natively over a directory and
in wasm over the
importshost functions — the handler logic above it never learns which. - Package
Handler - The format logic of one package handler. Everything here is pure over a
BlobStore, so the single implementation compiles for the host and forwasm32-unknown-unknownunchanged. A handler crate implements this once; the wasm crate beside it adds no logic, only the ABI shim.