Repository navigation
Release v0.36.0
0.36.0
Released 2026-09-22
Added
-
The default
wasi:httpoutbound backend validates theClient-Cert
bundle's first certificate — the TLS identity presented to the server —
before it is used: a leaf whose Extended Key Usage excludesclientAuth
(a server-only certificate), a CA certificate in the leaf position, a Key
Usage that does not permit signing, or a certificate outside its validity
window fails the request with an internal error before any connection is
attempted. Per RFC 5280 a leaf with no Extended Key Usage extension is
accepted; chain certificates after the leaf are not inspected. -
Guest-requested plugin loading: the
omnia:plugins/loaderhost capability.
A guest whose world imports it can ask the host to load a component at run
time —load(package, location, digest?)returns a plainpluginrecord
(id,digest), a value carrying no lifecycle authority over the loaded
component; component bytes never cross the interface in either
direction. The host pipeline is trust-ordered: idempotency on
(package, digest) → acquisition through the deployment's compiled-in
acquirer → sha256 pin verification (before any wasmtime validation;
unpinned loads report the resolved digest for trust-on-first-use) → typed
refusal of native/pre-compiled bytes → safeGuestArtifact::wasm
validation only → refusal unless the component exports a declared plugin
interface → registration under the package identity. Refusals are typed
by the caller's remedy (refusedfor a wrong request or deployment,
unavailablefor a retryable acquisition failure,already-activefor an
identity conflict,internalfor a host fault), with the description
naming the specific cause; a deployment guest's identity can never be
re-bound, and a conflicting re-pin of an active package refuses.
Acquisition policy is a pair of
composition-root slots, one per location kind (RegistrySource,
PathSource), installed throughPlugins::installfrom the
Wiring::extendhook; theruntime!macro's declarative
plugins: { locations: [...] }list (or a manifest's[[location]]
entries) carries them as deployment data (Location) that the
generated hook installs throughPlugins::install_declared. A load names
itsOrigin— a registry endpoint or a location-relative path — and
routes structurally by kind; an empty slot refuses typed. The built-in
acquirers ship inomnia-pluginand re-export:PathMounts(named
directory roots opened fail-fast at startup, read fresh on every load)
andRegistryClient(exactnamespace:name@versionreferences from a
declared default registry endpoint, verified against the registry's
content digest, fresh-release-preferred whenRegistryClient::cached
attaches a store —ContentStorefor digest-keyed bytes,ReleaseStore
for per-registry release records). The loader
links once on the shared linker when a deployment declaresplugins;
wasmtime wires it only
into guests whose world importsomnia:plugins/loader. See
docs/security-model.md. -
The requester surface for the loader, in
omnia-guest's newplugins
module: aPluginscapability trait (WASI-backed default body onwasm32
over the crate's ownomnia:plugins/loaderbindings, bare natively so
suites script loads), theWasiPluginszero-sized provider, shared
PluginRef/Digest/Locationtypes compiled on both targets (Digest
validates and canonicalizessha256:<hex>pins, with serde support), a
Pluginhandle carrying the routed identity and resolved digest, typed
refusals mirroring the WIT error variant with kebab-casecode()
discriminants and a conversion into the guestErrortaxonomy
(unavailable→BadGateway,internal→ServerError, every other
refusal →BadRequest). No consumer vocabulary anywhere: any
requester-class world can use it. -
hosts:rows accept compiled-in connect options:Host: Backend(options)
lowers toBackend::connect_with(options)instead of the env-sourced
Backend::connect(). Use it to compile configuration into the binary — a
fixed storage root (e.g. a project CAS path), or a scripted test backend
carrying state without statics. Rows sharing a backend type share one
connection, so their options must be written identically on every row (or
omitted on every row) — a mismatch is a spanned compile error, as are
empty parentheses.hosts: { WasiKeyValue: Filesystem(FilesystemOptions::at(".omnia/storage")), WasiBlobstore: Filesystem(FilesystemOptions::at(".omnia/storage")), }
-
omnia-test, published for the first time: test doubles, a component
runtime harness, and awasm32-wasip2fixture pipeline for code built on
omnia, behind three additive features with no default — a consumer names
the rung it uses, and a build-dependency line that forgets
default-features = falsestill pulls nothing butstd.guest: a native double peromnia_guestcapability trait —
Scripted: Model,ScriptedLoader: Plugins(per-package digests and
refusals, plusdefaulting(digest)for every unscripted, unpinned load
in place of the per-package placeholder),Memory: StateStore + BlobStore(withNamespaced; theBlobStoreExtoperations derive from
the primitives),MemoryDocsover the docstore default,
ScriptedTables,MatchedHttp,Sink: Publish + Broadcast,
MapConfig,FixedIdentity— plusguest::Provider, one struct
implementing every capability trait over tenpubfields holding those
doubles, each replaced through a consuming builder of the same name
(Provider::default().config(..).model(..)), withStateStoreand
BlobStoresharing onestoragefield so a handler bounded on both
sees the single backend a production provider has.host:Deployment, an overlay on a manifest — built from nothing with
Deployment::new()or from aruntime!module's compiled-in
manifest()— that rewrites the guest set, mounts, plugin interfaces,
the command guest and the.path location, and runs through the host
list you name (run_host,run) or the module's generatedHooks
(run_with).Backendsbundles the twelve in-memory defaults, each
swappable host a type parameter defaulting to its in-memory backend
(Backendsalone is the all-default bundle) with a consuming setter of
the same name —model(m)for anyWasiModelCtx, andkeyvalue,
blobstore,docstore,sql,vault,messaging,identity,
otelfor theirWasi*Ctx— so a production backend or a recording
wrapper runs under the same guests the defaults do; the bundle
implementsProvidesfor every host whatever it carries.defaults()
is deterministic — no environment
variable read, no socket opened: config answers from the map seeded by
Backends::config([..])alone, the websocket backend serves no
listener, and the HTTP client andSQLiteconnection (one private
:memory:database per bundle) are built from fixed options.
ScriptedModelscripts completions whose steps (tool calls and
workspaceread/write/list) run ahead of the answer, recording
every exchange and thelocal_pathlent per turn; an overrun answers
the guest softly and fails the test atassert_exhausted().Scratch
gives per-test directories and mounts.build,std-only:Componentsruns the nested cargo build a
consumer'sbuild.rsneeds and writesgen.rswith one path constant
per program and aforeach_<group>!completeness macro per group.
Programs are[[example]]targets (examples,scan) or the shipped
cdylibpackages themselves (packages,scan_packagesover a
directory of crates — constants read<GROUP>_<NAME>, arms are the
crate name), andextra_packagebuilds a driver guest beside them with
a constant but no arm. One build draws from any number of sources —
the shipped components and the programs that drive them land in one
gen.rs— withgroupnaming the source added just before it;
Programgainsconstantandexamplefields.
Both scripted models share oneScriptcore, so the same script reads
identically at the handler and component rungs.cargo make cinow checks
each feature alone and in combination (features,hack) and guards the
builddependency tree (tree-guard).
-
The command façade,
omnia_guest::api::command: the command-line mirror
ofapi::httpover the sameClient, so one handler fn serves HTTP, a
topic, and a shell without duplication.parse::<App>(argv) -> Parsed
classifies clap's outcomes — the grammar (App), help or version text for
stdout at exit 0 (Display), or a usage error (Usage) that
Response::usage(&error)renders atUSAGE_EXIT(64,EX_USAGE, so exit
2 always means aNotFoundenvelope).Command::new(&client, &metadata, format)is the projector:command.call(handler, decode, render).await -> Responseruns one verb as decode →Client::call→ encode, where
decode: FnOnce() -> Result<I, Error>builds the handler input from the
parsed grammar, a success body is encoded throughFormat::encode(with
renderas its text form) onto stdout at exit 0, and a decode or handler
error (F::Error: Into<Failure>) becomes theFailureenvelope on stderr
atError::exit_code()in the sameFormat;.hints(|error| ..)
attaches a remedy hint to every failure that carries none.Failurewraps
anomnia_guest::Error(From<Error>,From<anyhow::Error>) plus an
optionalwith_hint(..), serializes flat as
{"error","message","exit-code","hint"?}, and renders as text through
Failure::text(error[<code>]: <message>thenhint: <hint>).
Response { stdout, stderr, exit }(success(stdout)/
failure(stderr, exit)) buffers both channels and implementsIntoExit:
omnia_guest::command!(main)binds anasync fn main() -> Responseas
thewasi:cli/runexport (entries returningResult<(), u8>or()are
accepted too), runs it throughcommand::execute_wasiso telemetry is
initialized and flushed, and writes the channels at that boundary — a
BrokenPipeon either keeps the response's own exit, any other refused
channel exits 3 (ServerError).completions::<App>(shell, name)
produces a shell-completion script andclap_complete::Shellis
re-exported.Metadata::from_env(prefix)is the command line's carrier
for invocation metadata, reading<PREFIX>_REQUEST_ID/
_CORRELATION_ID/_CAUSATION_IDthroughMetadata::from_lookupso a
command guest is correlated the way an HTTP guest is through
x-request-id. The clap-backed parts (parse,completions,
Response::usage,Shell, and theclap::ValueEnumderive on
api::Formatfor a--format text|jsonargument) ship behind the new
omnia-guestcommandfeature (clap+clap_complete);Command,
Response,Failure, andcommand!need no feature. The e2e scenario
programs enter throughcommand!in place of the deleted
test_programs::run!; because the export importsomnia:otel,
omnia_test::host::Deployment::run_hostlinksWasiOtelbeside the host
under test and requiresB: Provides<WasiOtel>. -
Transport-neutral failure and encoding surface in
omnia_guest::api:
ErrorBody { error, message }is the one wire body for a failed
invocation (From<&Error>:code()→error,description()→
message);Error::exit_code()sits besideError::status()with the
1:1 exit map (BadRequest1,NotFound2,ServerError3,
BadGateway4);Format { Text, Json }with
Format::encode(&body, render)produces anEncoded { bytes, media_type }
— text through aFn(&T, &mut dyn fmt::Write) -> fmt::Resultrender
closure astext/plain; charset=utf-8, or pretty JSON with a trailing
newline asapplication/json— andEncodedimplements axum's
IntoResponse(200 with the media type asContent-Type). -
omnia-guesthttpfeature (default on): gates the axum-backed
api::httprouting, themcpserver, theHttpError/HttpResult
root re-exports, and the hiddenaxumre-export behind
dep:axum+dep:serde_urlencoded, so a command-only or messaging-only
guest built with--no-default-featureslinks no axum. The outbound
HttpRequestcapability,Error::status(), andapi::messagingstay
unconditional. -
An e2e
commandgroup incrates/test-programs(command/exit_map, a
command!guest onparse→Command::calloverMetadata::from_env)
driven bycrates/omnia-test/tests/command.rsthrough
test_programs::foreach_command!, proving the façade's exit plumbing
through the real runtime: a handler's error class reaches the host as its
mapped status (bad_request!1,not_found!2,bad_gateway!4, success
0) and an unknown verb exitsUSAGE_EXIT(64).test-programs' wasm32
build enablesomnia-guest'scommandfeature and depends onclap. -
omnia_wasi_websocket::WebSocketDefault::new()(andDefault) is public:
the backend without a listener, whoseconnect()still yields a client.
omnia_wasi_http::ConnectOptionsandomnia_wasi_sql::ConnectOptionsare
re-exported soconnect_withcan be called with fixed options instead of
connect()readingHTTP_CONNECT_TIMEOUT/SQL_DATABASE. -
omnia_wasi_otel::set_filter(directives): a guest replaces its tracing
filter with aRUST_LOGstring after its subscriber is installed, so a
guest-owned verbosity flag (--debug,--quiet) parsed from argv can
drive the guest's own tracing. The supplied directives are refined by the
guest'sRUST_LOG: valid environment directives are applied after them
and win for the same target, invalid ones are reported to stderr and
ignored. The subscriberinitinstalls now holds itsEnvFilterin a
reload::Layer; events after the call follow the new filter, and the
always-on transport mutes stay in force. -
Telemetry::filter(directives): an embedder filters the host console
subscriber by an explicitRUST_LOGstring instead of the environment
(the always-on dependency mutes still apply). -
Metadata over the dispatch chain.
ChainCtxcarries name/value entries
(metadata(),metadata_mut()) that every guest dispatched beneath the
hop that set them is dispatched with; the runtime core never interprets
them. Each hop's context is a snapshot taken atChainPolicy::enter, so
entries set after a dispatch never reach it.wasi-otelshows them to a
guest as its baggage:omnia_wasi_otel::set_baggage(entries)(the new
omnia:otel/baggageinterface) extends the chain's metadata, an entry
replacing the value under its name and leaving other names in place, and
omnia_wasi_otel::baggage()reads what the chain currently carries, with
or without telemetry initialised. -
The tracing level crosses the dispatch chain.
omnia_wasi_otel::LEVELis
the baggage entry (tracing.level) naming the level the chain's guests
open at; a guest that has chosen its level names it for the guests it
dispatches withset_baggage([(LEVEL, "info")]), beside theset_filter
reload of its own subscriber. Every guest dispatched beneath it opens its
subscriber at that level as its telemetry initializes — before its
outermost#[instrument]span, so a callee's boundary span is admitted at
the level its caller asked for with no reload of the callee's own — with
validRUST_LOGdirectives applied on top as before. A chain that names
none opens the callee aterror, as a root opens; an entry that is not a
level is reported to stderr and read aserror.omnia_wasi_otel::level()
reads the level the chain carries, so a callee that filters per crate
composes its own directives over it. A level crosses, never directives. -
The dispatch-chain context lives on the guest store. Every store is built
at aChainCtx(StoreConfig::chain,StoreBase::chain):
Runtime::store()builds a server root,Runtime::store_in(chain)any
other, and aStoreFactorytakes the context its callee runs at, so the
command driver, the trigger hosts, andcall_freshcarry no ambient
scope. The link relay snapshots the calling guest's context from its store
throughHasChain(implemented byStoreCtxbesideHasMountsand
HasExtensions),ChainPolicy::enter(&caller, &target)derives the
callee's, andDispatcher::invoketakes the caller's context ahead of
the target. A host binding reaches the context through the same
HasChainbound, asomnia:otel/baggagedoes.
Changed
-
Upgraded
wasmtimeand the matchingwasmtime-wasi*crates to 49.0.0.
wasmtime-wasiis linked with thep2andp3features only, so
omnia::wasmtime_wasi::p1no longer exists, andwasmtime-wasi-iois
dropped from the workspace. Pre-compiled.binartifacts must be rebuilt
withomnia compile: the engine version and the now-default
wide-arithmetic flag both change the compatibility check, so a 48-built
artifact is rejected at load. Thewasi:httpserver builds the guest
request through upstreamRequest::from_httprather than a hand-mirrored
copy of it (the+ use<T>capture fix in 49 made the mirror unnecessary);
behaviour is unchanged. -
ChainCtxisClone, notCopy, and no longerDefault: it now
carries the chain's metadata, and a root isChainCtx::server()or
ChainCtx::command(); code that relied on the implicit copy clones or
borrows.as_command_chain(fut)is now a store built at the command
root,runtime.build_store(runtime.store_in(ChainCtx::command())), and
Dispatcher::invoketakes the caller'sChainCtxbefore the target.
Every guest that runs the telemetry lifecycle — an#[instrument]ed
export, acommand!entry — importsomnia:otel/baggage, since its
subscriber opens at the level the chain carries, and so needs a runtime
that carries it;WasiOteldoes, as does the no-opOtelDefaultbundle
beneath it. A guest with no telemetry links unchanged, the componentizer
having pruned the import. -
The
omnia-sdkormfeature is split intosql(theTableStore
capability overwasi:sql) anddocstore(theDocumentStorecapability
and thedocument_storere-exports), both default alongsidehttp. A
guest that disabled defaults and namedormnow names the capability it
uses; a default build is unchanged. In-repo consumers follow (examples:
sql,docstore,http,command;omnia-test/guest:sql,
docstore), andcargo make featuresgates each new feature alone. -
Guest telemetry has one lifecycle entry point:
omnia_wasi_otel::scope(f)
initializes telemetry (once per instance), awaits the futurefbuilds,
and exports every buffered span and recorded metric before returning; a
nestedscopeis a pass-through.#[instrument]on anasync fnand
command!both route through it, so the export completes ahead of an
exit-with-code.init,flush_guard, and theExitGuarddrop guard
are gone;flush()stays for a guest that exports mid-run.#[instrument]
on a syncfnnow only opens the span (a sync function is never an
export). Guest console log lines go to stderr, as the host's do, so
stdout stays the guest's own output (command-mode pipes and JSON
envelopes); they carry no span prefix, because the fmt layer receives
events only, so fields such ascorrelation_idare not repeated on every
line and the default coloured format is otherwise unchanged. The guest
filter's one always-on mute is the OpenTelemetry API's own target
(opentelemetry=off), keeping its self-diagnostics out of guest output
should a guest dependency enable them; the host-transport mutes (hyper,
h2,tonic), which no guest links, are gone. Exported guest spans no
longer carrythread.id/
thread.nameattributes (a guest is single-threaded), and a subscriber
install that fails (the guest set its own) is attempted once per instance
rather than by every nestedscope. -
Guest metrics export with delta temporality: every invocation is a fresh
instance, so a cumulative total restarted on each export and equal counts
from successive invocations read as "no change" rather than as
increments. Up-down counters stay cumulative, as the OpenTelemetry SDK's
delta preference prescribes; a collector needing cumulative series
converts withdeltatocumulative. -
Guest telemetry no longer links the OpenTelemetry SDK. On
wasm32,
omnia-wasi-otelimplements theopentelemetryAPI traits that the
tracing-opentelemetrybridge and theglobal::tracer/global::meter
registries drive (TracerProvider,Tracer,Span,MeterProvider,
InstrumentProvider) directly over theomnia:otelrecords: a span
becomes aspan-datarecord as it ends, and the synchronous instruments
aggregate into per-scope series that the flush collects into one
resource-metricsrecord. Retained: the API and the bridge (a guest's
opentelemetryandtracingcode compiles unchanged), delta counters /
histograms / gauges with cumulative up-down counters, the default
histogram bounds andwith_boundaries, and the wire protocol (the WIT
and the host crates are untouched). Changed: observable (callback)
instruments record nothing (the API's no-op defaults); spans have no
attribute / event / link limits, so the dropped counts are always zero;
no exemplars; a second build of an instrument shares the first's series,
description, and unit; histogram bounds that are not finite and strictly
increasing yield an instrument that records nothing and reports once at
error; and theopentelemetry_sdk=offmute leaves the guest filter
with its target. The SDK and itsrand/rand_chacha/rand_core
tail leave every guest's dependency graph (trace and span ids are drawn
fromgetrandom, overwasi:random); the releaseotel_metrics_counter
guest shrinks from 1,085,552 to 865,767 bytes (−20%). -
Host-side
wasi:otelexport fixes: eachScopeSpans.schema_urlnow
carries the instrumentation scope's schema URL rather than the resource's
(the metrics path already did); the exported metrics resource no longer
gains aschema_urlattribute (with a null value when unset) beside the
ResourceMetrics.schema_urlthat already carries it; and guest spans
dropped because no host span is live (the trigger hosts open theirs at
DEBUG) are reported by onewarn!per process instead of adebug!per
export. Spans are grouped per scope by a linear scan over the handful an
export carries; the hand-writtenHash/Eqimpls on the generated
types (whosef64arms broke theEqcontract on NaN and signed zero)
are gone, along with the unused conversions for the never-implemented
context()import on both sides of the boundary. -
The host console filter is
RUST_LOGalone, on every entry path, and an
unsetRUST_LOGnow meanswarnrather thanerror, so host warnings
(a mount that failed to preopen) reach stderr without a hand-written
filter. A failed telemetry flush — every exit of a collectorless
command-mode run — reports atdebuginstead ofwarn. The
direct-command host log flags (--debug/--quietpeeled from argv)
and theLogModepresets behind them (omnia::LogMode,
Telemetry::log_mode,DeploymentBuilder::log_mode) are gone: a
direct-command guest sees every argument, and a product that wants those
flags defines them in its own grammar and applies them through
omnia_wasi_otel's filter reload APIs. -
omnia_test::build::Componentsruns its nestedwasm32-wasip2build into
target/wasm32-fixtures, a sibling of the outer profile directory shared by
every outer feature set, profile, and build-script hash, rather than under
the consumer's per-hashOUT_DIR, whose hash — and with it the fixture tree
— changed on every package version bump, toolchain update, and build
dependency change, leaving the previous tree behind for nothing to reuse or
remove. The nested build also setsCARGO_PROFILE_DEV_DEBUG=0: wasmtime
reads no guest DWARF unless asked, so the fixtures carry none. -
omnia-guestrenamed toomnia-sdk: the crate lives at
crates/omnia-sdkand guest code importsomnia_sdk::….
omnia-guest-macrosand theomnia-testguestfeature keep their
names. -
Guest→guest link dispatch now hands lifted
Vals to a fresh callee
task in memory — no encode, decode, or pipe. Importer and exporter
signatures are type-checked structurally by name, so a WIT skew
between them is refused at serve time or at lower time instead of
being positionally mis-decoded. Handle-carrying signatures
(own/borrow/future/stream/error-context) are
refused when the import is polyfilled. Host→guest
Dispatcher::invokenow propagates dispatch depth into the callee
and is bounded byGUEST_TIMEOUT_MSon server-rooted chains; a
timed-out or cancelled caller aborts the callee. A 1 MiBstring
round tripfull → echoer → full(fresh instances per call) went
from ~1.52 ms p50 / ~1.57 ms mean on wRPC to ~1.12 ms p50 /
~1.15 ms mean on in-memory routing, about 26 % less per call
(p99 ~1.93 ms → ~1.36 ms). The callee task keeps the dispatcher's
tracingspan current, so a linked guest's telemetry export grafts
onto the live host span the way an inline drive's does; before, the
spawned task had no current span and the host dropped every span a
callee exported. -
The registry location carries the deployment's routing policy. A
{ registry: ..., config: ... }entry (or[[plugin.location]]with a
configkey) pairs the default endpoint with a wasm-pkg client
configuration as TOML —namespace_registries,
package_registry_overrides, per-registry backend settings — typically
include_str!("wasm-pkg.toml"), so the compiled binary alone attests every
registry a package may resolve to.Location::Registrygains the optional
configfield andLocation::registry_config(registry, config)beside
Location::registry(registry);Plugins::install_declaredparses the
configuration when the runtime assembles, refusing malformed TOML or a
default_registrythat disagrees withregistry.RegistryClientresolves
a package to the load's explicit endpoint, else its package override, else
its namespace's registry, else the default, and pins the wasm-pkg client to
that registry per fetch;RegistryClient::with_configis how a hand-built
acquirer supplies the same routing.plugin: { locations: [ { name: ".", path: "." }, { registry: "omnia.host", config: include_str!("wasm-pkg.toml") }, ], },
-
Every invocation is observable.
Metadata::from_lookupmints a request id
when the transport carries none — 32 lowercase hex chars from
wasi:randomonwasm32(astd::hash::RandomState-derived id natively,
for tests) — so HTTP (x-request-id), messaging metadata, and the command
line's environment all yield arequest_id, with the correlation id still
falling back to it;Metadata::default()stays all-None.Client::call
runs the handler inside ahandlertracing span carryingrequest_idand
correlation_id. -
api::http::handle_withdecoders return any errorInto<HttpError>
(D: Fn(RawRequest<'_>) -> Result<I, DE>withDE: Into<HttpError>)
rather than onlyDecodeError, so an HTTP decoder classifies its refusal
(anot_found!path parameter answers 404 with the JSONErrorBody) as
a command decoder already does. The JSON conveniences (get,post, …)
keepDecodeError; a decoder closure that uses?now names its return
type (|raw: RawRequest<'_>| -> Result<Input, DecodeError> { .. }). -
HttpError::from(omnia_guest::Error)now emits the JSONErrorBody
({"error":"<code>","message":"<description>"},application/json) at
the variant's status instead of a plain-textcode: …, description: …
body, so HTTP clients read the sameerrordiscriminant every transport
emits. This also coversFrom<DecodeError>(400 with
error == "invalid_request") and theanyhow::Errorconversion when its
chain contains anomnia_guest::Error; a foreignanyhowerror still
produces a plain-text 500. -
omnia_guest::api::commandcompiles on every target: onlyexecute_wasi
(thewasi:cli/rundriver) and thecommand!macro that expands to it
stay wasm32-only, soIntoExit,Command,Response, andFailureare
testable natively. -
omnia-guestdefaults are now["orm", "http"]. A guest that already
disables default features and routes HTTP must addfeatures = ["http"];
a default build is unchanged. -
The workspace-internal
omnia-guestdependency entry carries
default-features = false, following theomnia-core/omnia-test
precedent, and in-repo consumers name the rungs they use (examples:
orm,http,command;test-programs:command;omnia-test/guest:
orm). Previously theomnia-testdev-dependency unifiedormand
httpback into everycargo clippy -p omnia-guest --no-default-features --all-targetsline, so thefeaturesgate checks were vacuous; the
httpandcommandgates are now real (omnia-test/gueststill carries
ormbecause its doubles wrap the ORM traits). External consumers are
unaffected: the crate's owndefault = ["orm", "http"]is unchanged. -
The
examples/cliguest (shared bycli-static) runs on the command
façade:command!(main)overparse→Command::new(..).call(handler, decode, render), with a global--format text|jsonand
Metadata::from_env("CLI"). Its hand-writtenCommandErroris gone —
handlers returnomnia_guest::Error— so exit codes now follow the exit
map rather than being chosen by the guest:fail [CLASS]takes
bad-request/not-found/server-error(default) /bad-gateway
and exits 1 / 2 / 3 / 4 with theFailureenvelope on stderr, and a clap
usage error (bogus) exits 64 instead of 2. The smoke scenarios and both
READMEs follow. -
Guest handlers are fns bound at the route, over an owned context.
omnia_guest::api::Context<P>drops its lifetime andCopy: it owns
Arc<str>/Arc<P>clones plus theMetadata, exposesowner()and
provider()accessors over private fields (the publicmetadatafield
stays), and gainsContext::new(owner, provider, metadata)so a handler
can be unit-tested without aClient.Handler<P>implemented on the
input type becomesHandler<P, I>with one blanket impl over every
async fn(I, Context<P>) -> Result<O, E>(and everyCloneclosure of
that shape, so a closure can carry configuration into a route); the
trait method iscall(self, input, context), and implementing it by hand
on a local non-fn type is the documented escape hatch. Route
constructors take the handler value instead of a type parameter:
get(handler)/delete(handler)/post(handler)/put(handler)/
patch(handler),handle_with(filter, handler, decode, encode)with the
decoder returningResult<I, DecodeError>and the encoder
Fn(F::Output) -> Response,consume(handler), and
consume_with(handler, decode);
Client::call(handler, input, &metadata)takes the handler first.
Nothing on the wire changes (JSON envelope,HttpError,DeliveryError,
exit codes). The#[omnia_guest::handler]proc-macro and its
omnia_guest::handlerre-export are deleted — rustc's own diagnostics
name a mis-shaped fn — andomnia-guestno longer depends on
omnia-guest-macros, which now provides only#[instrument](still
reaching guests asomnia_wasi_otel::instrument).// before // after impl Handler<P> for CreateItem { async fn create_item( type Output = ItemReply; input: CreateItem, context: Context<P>, type Error = Error; ) -> Result<ItemReply, Error> { async fn handle( let cfg = context.provider().config(); self, context: Context<'_, P>, // ... ) -> Result<ItemReply, Error> { } let cfg = context.provider.config(); // ... } } .route("/items", post::<CreateItem, P>()) .route("/items", post(create_item)) .route("t", consume::<CreateItem>()) .route("t", consume(create_item)) client.call(input, &metadata) client.call(create_item, input, &metadata)
-
omniais the composition root (deployment assembly, process
lifecycle, optional-crate composition);omnia-coreis the
live-runtime SDK a capability crate targets;omnia-cliis a leaf
grammar crate with noomnia-*dependencies.DeploymentBuilderis
no longer typestate-parameterized:buildis the safe wasm path and
unsafe build_trustedreplaces thePrecompiledtypestate's unsafe
build(WasmOnly/Precompiledandprecompiled()are deleted).
omnia::run/run_withtake a builtDeployment(the
runtime!-generated wrappers still take aDeploymentBuilderand
build first);run_precompiledis deleted.Runtime::new/
with_backendsand their link/extend closures are deleted:
Deployment::assemblebuilds aRuntimefromRuntimePartsvia
Runtime::from_parts. Artifact loading is oneGuestArtifact::load;
Sourcedecides trust by content (native bytes never reach wasmtime
on the safe path), andRuntime::admitno longer sniffs ELF itself.
Manifest,DeploymentBuilder,Deployment,Wiring,Backends,
Mode,run/run_with,main, andcompilelive inomnia;
omnia-corekeeps the live handle (Runtime,Registry,admit,
Extensions, dispatch, telemetry). -
The
runcommand-line grammar moves out ofomnia-coreinto a new
omnia-clicrate, which theomniafacade'sclifeature now selects
(cli = ["dep:omnia-cli"]).omnia-coreno longer has aclifeature or
aclapdependency; its entry point serves direct commands only.
omnia_core::{Cli, Command, Parser}are nowomnia_cli::{Cli, Command, Parser}— the facade pathsomnia::Cli,omnia::Command, and
omnia::Parserare unchanged, as is everything theruntime!macro emits.
A direct-command binary built with--no-default-featuresnow links no
clap. -
The
omnia:plugins/loadercapability (omnia-plugin) sits behind a new
non-defaultpluginfeature on theomniafacade
(plugin = ["dep:omnia-plugin"]), mirroringcli. The facade paths
(omnia::WasiPlugins,omnia::Plugins,omnia::PathMounts,
omnia::LoadError, …) and theruntime!syntax are unchanged; a
deployment that declaresplugins: { locations: [...] }, or a bare
plugins: {}besideconfig:for the TOML's[[location]]entries,
must enable it (omnia = { version = "...", features = ["plugin"] }) or
the expansion fails to compile namingomnia::WasiPlugins. A default
build carries no loader and no HTTP/OCI registry stack (wasm-pkg-client
and its ~120 transitive crates are gone), and refuses a manifest with
[[location]]entries at startup. The macro links the loader host and
emitsWiring::extendonly for those two shapes: an interfaces-only
plugins: { interfaces: [...] }block no longer links a loader that
refused every load, so a guest importingomnia:plugins/loaderin such a
deployment now fails at instantiation rather than per load; a bare
plugins: {}withoutconfig:is now a compile error instead of a
silent no-op.omnia::LoadErroris owned byomnia-pluginrather than
being thebindgen!-generated WIT error: same variants,Display, and
Clone/Debug/PartialEq/Eq/Error, minus wasmtime's
Lift/Lower/ComponentTypeimpls. -
Every
omnia-guestcapability trait is implemented forArc<T>,&T,
andBox<T>whereTimplements it, forwarding on both targets, so a
provider field may hold a double behind a shared handle and a
P: StateStorebound acceptsArc<Memory>with no wrapper struct. -
BlobStoresplits: the ten primitives (get,put,delete,list,
get_range,object_info,create_container,delete_container,
container_exists,container_info) stay onBlobStore;has,
delete_objects,clear,copy_object, andmove_objectmove to
BlobStoreExt, a blanket-implemented extension trait whose native bodies
compose the primitives and whosewasm32bodies remain one host call
each. Migration: delete those five methods from any hand-written
BlobStoreimpl (they are no longer trait members) and import
omnia_guest::BlobStoreExtwhere a call site uses them.Memoryand
Namespacedinomnia-testdrop their overrides accordingly. -
The
omniafacade is the whole embedder dependency, in documentation as
well as in code. Its re-exports are#[doc(inline)], so rustdoc renders
every runtime and plugin item asomnia::…instead of a wall of
pub use omnia_core::…lines whose pages — and every path a reader copies
from them — spelled the underlying crate;WasiPluginsCtxViewjoins the
plugin re-exports so that surface is complete. The generated runtime
module importsomnia::anyhow::Resultrather thananyhow::Result, so
runtime!no longer requiresanyhowin the embedder's ownCargo.toml
(omnia::anyhowis now a documented re-export, being the error vocabulary
ofBackendandWiring, as isomnia::futures, whoseBoxFuturethe
ContentStore/ReleaseStore/PathSource/RegistrySourceseams return).
omnia-plugin's docs no longer tell store
implementors to depend on it directly —ContentStore/ReleaseStoreand
theBackend/NoOptionsacache:store also needs all reach embedders
throughomnia; depending onomnia-coreoromnia-pluginis only for
building another capability crate. -
Plugin loads are lock-free and race-safe. The loader's (package, digest)
idempotency record now rides the registry entry itself (Guest::digest,
recorded byRuntime::admitfrom the admitted bytes), so the attestation
can never outlive or misdescribe the guest it names — an identity
deregistered and re-registered by the embedder no longer answers a pinned
re-load with a stale digest. The loader's shadow digest map and the global
load mutex are deleted; concurrent loads race throughadmit, whose
atomic publication reports the loser via the new
AdmitError::AlreadyRegisteredvariant, resolved against the winner's
recorded digest (idempotent success on a match,already-active
otherwise).sha256_digestmoves toomnia-core(still re-exported from
omnia). -
Acquirers refuse honestly:
RegistrySourceandPathSourcereturn the
loader's typedLoadError—refusedfor an authoritative "no" (a
malformed reference, a package or path the source does not serve),
unavailablefor a failure a retry may clear — so an unknown package no
longer reports as retryable. -
The runtime core drops "plugin" from its vocabulary: the manifest accessor
Manifest::plugin_interfaces()is renamed tolink_interfaces(), and
admission refusals name "link interfaces". The config surface is
unchanged — the TOMLplugins = [...]list, the macroplugins:block,
and the CLI--pluginsflag keep their names; onlyomnia-pluginspeaks
"plugin". -
The
omniacrate splits intoomnia-core(the runtime spine: deployment,
registry, dispatch, stores, telemetry, CLI) and a thinomniafacade that
re-exports core,omnia-plugin, and theruntime!macro under the
existing paths — embedder imports are unchanged.omnia-pluginis now the
whole plugins capability: the loader WIT andWasiPluginshost binding,
thePluginsload path, digest policy, and the acquisition seam all
live there, built on two intentional core seams that future capability
crates reuse:Runtime::admit(the one privileged operation — safe
validation, seam-export check, atomic registration; typedAdmitError)
and theExtensionstypemap (capability state installed by the new
Wiring::extendhook — replacingWiring::acquirer— and read back from
stores throughHasExtensions; state that calls back into the runtime
holds aWeakRuntimeviaRuntime::downgrade).Runtime::new's third
parameter is now the extend hook (FnOnce(&Runtime<B>) -> Result<()>);
StoreConfig::loader/StoreBase::loaderare replaced by theextensions
handle, and the loader's digest record rides the registry entry itself
(Guest::digest, recorded at admission), living and dying with the guest
it attests. -
Host wiring is trait-carried, not name-derived (pre-1.0 hard cut, no
aliases). Omnia core gainsHostCtx(the host's borrow shape and view
assembly,Borrow<'a>GAT +view),Provides<H>(the one
bundle-accessor trait), andStoreView<H>(the one store-side view trait,
blanketed onStoreCtx<B>for everyB: Provides<H>). The per-host
Wasi*Viewtraits,Has*accessor traits, and per-hostStoreCtx
blankets are deleted;wasi_view!now emits theCtxViewstruct plus the
host'sHostCtximpl, and hostadd_to_linkeraccessors ride
T: StoreView<WasiX>/T::view. Theruntime!macro emits one uniform
impl omnia::Provides<...> for Backendsperhosts:row — no more name
surgery from the host ident, so re-exports and third-party hosts wire the
same way as first-party ones, andwasi:config's shared-borrow shape is
carried by its ownHostCtximpl instead of a codegen special case. One
special case survives, documented in codegen:wasi:http's view trait is
foreign (wasmtime-wasi-http), so its row is keyed to the core-owned
omnia::HttpCtxcarrier (HasHttpis replaced by the backend-level
HttpBorrowtrait), and a dodged match now fails at compile time rather
than silently. Hand-built bundles implementProvides<WasiX>directly.// before // after impl HasModel for Backends { impl omnia::Provides<WasiModel> for Backends { fn model_ctx(&mut self) fn borrow(&mut self) -> &mut dyn WasiModelCtx { -> &mut dyn WasiModelCtx { &mut self.0 &mut self.0 } } } }
-
The
runtime!macro serves everyhosts:row uniformly through
Server::run: capability hosts resolve immediately via the no-op default,
trigger servers loop until shutdown. The macro's string-matched server
list and the unreadServer::IS_SERVERconst are deleted; a third-party
trigger host'srunis now actually served instead of silently skipped. -
The
runtime!macro'splugins:key grows from a bracketed list to a
block:plugins: { interfaces: [...], locations: [...] }. The
declarativelocations:list is the acquisition policy — named path roots
and at most one registry endpoint — carried as manifest data
(Manifest::locations,Location;[[location]]inomnia.toml)
and installed by the generatedWiring::extendthrough
Plugins::install_declared, which folds path entries intoPathMounts
and the registry entry into a cachelessRegistryClient, slotted by
location kind. Both keys are optional (a deployment that only does
host-mediated dispatch needs no acquirer; loads then refuse typed at run
time); an emptylocations:list and a secondregistryentry are
spanned compile errors, and the bare-list form is a compile error naming
the block shape. Declaring the block also links theomnia::WasiPlugins
loader host. Because locations are manifest data they conflict with
config:like every other inline key; a config-file deployment declares
[[location]]entries in the TOML. The bundle-carriedcache:option is
gone: a store-backed registry acquirer is installed by hand —Plugins::installover
RegistryClient::new(..).cached(store)from a customWiring::extend.// before // after plugins: ["omnia:link/echo"], plugins: { interfaces: ["omnia:link/echo"], locations: [ // optional { name: ".", path: "." }, { registry: "ghcr.io" }, ], },
-
The
runtime!macro's wiring is reachable from tests. The generated
Hooksis nowpuband generic —impl<B> Wiring<B> for Hooks where B: Provides<...>for each declared host — and two items joinmainand
runin the re-export:manifest()returns the compiled-in
ManifestSource, andasync fn run_with<B>(builder, backends)drives a
deployment throughHooksover a bundle already in hand, connecting
nothing. Behind them,Wiring<B>is bounded onB: Clone + Send + Sync + 'staticinstead ofBackends(aBackendsbundle still satisfies it),
omnia::run_withandRuntime::with_backendsare the connect-free twins
ofrunandRuntime::new, andRuntime::plugin_locationsexposes the
deployment's declared locations.omnia_test::host::Deploymentbuilds on
this as an overlay:Deployment::from(runtime::manifest())takes the
binary's manifest as its base,path_rootrewrites the.location, and
run_with::<runtime::Hooks, _>(backends)runs the productionlink,
extend, andserveoveromnia_test::host::Backends.mod runtime { omnia::runtime!({ plugins: { locations: [{ name: ".", path: "adapters" }] }, guests: [...], hosts: {...} }); } let status = Deployment::from(runtime::manifest()) .path_root(scratch.path()) .run_with::<runtime::Hooks, _>(Backends::defaults().await) .await?;
-
The host-mediated interface list is renamed from
dispatchtoplugins:
a top-levelplugins = [...]in TOML, a top-levelplugins: [...]key in
theruntime!macro, the fluentManifest::plugins(...)setter (with the
Manifest.pluginsfield and theManifest::link_interfaces()accessor),
and the CLI flag--plugins(replacing--dispatch, no alias). A
leftover top-leveldispatch(orlink), orpluginsmisplaced on a
guest entry, is a parse/compile error. Behavior is unchanged: listed interfaces are polyfilled onto
the shared linker at assemble (an exporter may arrive later via
Runtime::register), and the selector still picks the target guest by
routing id at call time. The dispatch mechanism keeps its names —
crates/omnia/src/dispatch,serve_links,DispatchHandle,
MAX_DISPATCH_DEPTH, and theomnia:linkWIT packages are untouched.# before # after dispatch = ["omnia:link/echo"] plugins = ["omnia:link/echo"]
-
The host-mediated interface list is renamed from
linktodispatch
and is deployment-wide only: a top-leveldispatch = [...]in TOML, a
top-leveldispatch: [...]key in theruntime!macro, the fluent
Manifest::dispatch(...)setter, and the CLI flag--dispatch(replacing
--link, no alias). The per-guest form (GuestEntry.linkin TOML,
link:on a macro guest entry,GuestEntry::link()) is removed — the
linker is shared, so per-guest lists always flattened into one
deployment-level grant and never enforced a per-guest ACL. A leftover
link(top-level or per-guest) or adispatchmisplaced on a guest
entry is a parse/compile error. Behavior is
unchanged: listed interfaces are polyfilled onto the shared linker at
assemble (an exporter may arrive later viaRuntime::register), and the
selector still picks the target guest by routing id at call time. WIT
packages (omnia:link) and internals (serve_links,
DispatchHandle::links) keep their names.# before # after [[guest]] dispatch = ["omnia:link/echo"] id = "router" source.path = "./router.wasm" [[guest]] link = ["omnia:link/echo"] id = "router" source.path = "./router.wasm"
-
Routes are now guest-owned: each
[[guest]]entry declares the routes
targeting it (routes.http/routes.messaging/routes.websocket
pattern lists in TOML, aroutes: { http: [...], ... }block per guest
entry in theruntime!macro,GuestEntry::route_http/
route_messaging/route_websocketprogrammatically), with the
declaring guest as the implicit target. The top-level[[route.*]]
tables, the macro's top-levelroutes:key, theManifest::route_*
setters, and theRouteSpec/HttpRoute/TopicRoutetypes are
removed; a stale top-level route section now fails manifest parsing.
Routing behavior is unchanged: per-guest lists aggregate into the same
per-trigger tables (longest-prefix HTTP, first-match NATS-style
patterns, capability catch-all when a trigger has no routes).# before # after [[guest]] [[guest]] id = "api" id = "api" source.path = "./api.wasm" source.path = "./api.wasm" routes.http = ["/api"] [[route.http]] prefix = "/api" guest = "api"
-
Removed the
runtime!macro'sprogram:key:mode: commandwith a
compiled-in deployment (config:or inline manifest keys) is now a
direct command by default — raw argv passthrough, nothing reserved for
the host, no hostrungrammar. The program name (telemetry and guest
argv[0]) defaults to the manifest name (first[[guest]]id).
Command-mode binaries without a compiled-in deployment keep therun
grammar -
Removed the
command_guest:key and its plumbing
(DeploymentBuilder::command_guest,Runtime::with_command_guest,
MainOptions::command_guest): command mode routes to the sole static
wasi:cli/runexporter, or to the guest entry markedcommand = true
(macrocommand: true,GuestEntry::command()); at most one guest may
carry the mark. The resolver-supplied command guest (fully dynamic
deployment, empty guest set) is gone with the key — a direct command
always compiles its manifest in -
Removed the late-binding deployment plumbing: the resolve-on-miss pull
layer (GuestResolver,Runtime::ensure_guest, the single-flight
machinery, macroresolver:,DeploymentBuilder::resolver,
Runtime::with_resolver), thehttp_pathstrigger hook (macro
http_paths:,DeploymentBuilder::http_paths,
Runtime::with_http_paths,RoutingPolicy/ table-only routing,
Runtime::route_http,RouteRefusal), and pre-bound HTTP listener
adoption (macrohttp_listener:,DeploymentBuilder::http_listener,
Runtime::take_http_listener). A registry miss is a dispatch error and
an unrouted HTTP path is a 404; the HTTP trigger always binds
HTTP_ADDR. Push registration stays:Runtime::register/deregister
andDeploymentBuilder::dynamic()are the way a registry grows after
boot, with registered guests reachable via host-mediated link dispatch
andDispatcher::invoke
Removed
- The guest ORM: the
omnia_sdk::ormmodule (Entity, theSelect/
Insert/Update/Deletebuilders,Filter,Join,FetchValue)
and theentity!macro, with thesea-queryandchronodependencies
they carried. The layer now lives inomnia-orm
(augentic/omnia-extensions),
which builds onomnia_sdk::TableStoreunder thesqlfeature; the
examples/sqlguest executes parameterized SQL throughTableStore
directly. - The
wrpc-transport/wrpc-wasmtimedependencies and the
[patch.crates-io]git override that unpublishedwrpc-wasmtime
required. Theomnia-corewrpcfeature is gone, as are the
omnia::{LinkClient, WrpcState, WrpcView, WrpcCtxView}and
omnia_link::{LinkTransport, InProcess}types.LinkStoreis
relaxed toWasiView + 'static. Downstream consumers
(omnia-exemplar and its guest template, emery-adapters,
omnia-backends, sample-cli): on the next omnia bump drop the
wrpc-*[patch.crates-io]entries, thedeny.tomlallow-git
forbytecodealliance/wrpc, and thecargo vetgit policies for
wrpc-introspect/wrpc-transport.
What's Changed
- Bump to 0.36.0 by @augentic-releases[bot] in #222
- Release 0.35.0 by @andrewweston in #223
- Improve telemetry by @andrewweston in #224
- Simplify telemetry by @andrewweston in #225
- Model answer by @andrewweston in #226
- Add model helpers by @andrewweston in #227
- Http routing by @andrewweston in #228
- remove timeout wrapper on CLI by @andrewweston in #229
- Refactor by @andrewweston in #230
- Add CLI debug flag by @andrewweston in #231
- No timeout for CLI by @andrewweston in #232
- Add feature flags by @andrewweston in #233
- unit -> integration tests by @andrewweston in #234
- Refactor docs by @andrewweston in #235
- Workspace grants by @andrewweston in #236
- wasi:model workspace grants by @andrewweston in #237
- Improve answer extraction by @andrewweston in #238
- Fix extraction fence bug by @andrewweston in #239
- Add operator macro by @andrewweston in #240
- remove unecessary test and fix deny warnings by @andrew-goldie in #241
- Runtime macro by @andrewweston in #242
- Simplify command router by @andrewweston in #243
- KeyValue CAS by @andrewweston in #244
- Error enum by @andrew-goldie in #245
- Model stream by @andrewweston in #246
- Seamless by @andrew-goldie in #247
- Curate wasi-model by @andrewweston in #248
- Simplify Guest API by @andrew-goldie in #249
- wasi-model tweaks by @andrewweston in #250
- wasi-model testing by @andrewweston in #251
- Wasmtime 48 by @andrewweston in #252
- Plugin support by @andrewweston in #253
- Address runtime! macro fragility by @andrewweston in #254
- Guest api tidy by @andrew-goldie in #255
- Remove docker by @andrew-goldie in #256
- Relocate plugins by @andrewweston in #257
- Remove legacy code by @andrewweston in #258
- Example tidy by @andrew-goldie in #259
- Fully separate omnia-plugin by @andrewweston in #260
- Example runs by @andrew-goldie in #261
- Plugin simplification by @andrewweston in #262
- remove allow lint added by agent in PR #257 by @andrew-goldie in #263
- Smoke tests for examples by @andrew-goldie in #264
- Remove core from docs by @andrew-goldie in #265
- Public omnia-test crate by @andrewweston in #266
- omnia-test fixes by @andrewweston in #267
- Cli crate by @andrew-goldie in #268
- Fix boundary between omnia and omnia-core by @andrew-goldie in #269
- Tidy plugin module organization and create feature flag by @andrew-goldie in #270
- Function-bound handlers by @andrew-goldie in #271
- Emery fixes by @andrewweston in #272
- More ergonomic SDK for CLI guests by @andrew-goldie in #273
- Guest callbacks for model answer verification by @andrewweston in #274
- omnia-link crate and feature flag for wRPC comms between guests by @andrew-goldie in #275
- Model answer fixes by @andrewweston in #276
- Wasi tests by @andrew-goldie in #277
- Macro trim by @andrew-goldie in #278
- Replace examples smoke test with minimal alternative by @andrew-goldie in #279
- Modify omnia-test to support other repos by @andrewweston in #280
- Use wasm-pkg registry configuration by @andrewweston in #282
- Remove wrpc by @andrew-goldie in #283
- rename omnia-guest crate to omnia-sdk by @andrew-goldie in #284
- Remove plugin cache from guest sdk by @andrew-goldie in #285
- No macro tests by @andrew-goldie in #286
- Optimise test build by @andrewweston in #287
- Bugbot build fix by @andrewweston in #288
- Cache to extensions by @andrew-goldie in #289
- Remove --debug flag peeling by @andrewweston in #290
- Remove span from traces by @andrewweston in #291
- Improve async guest support in otel by @andrewweston in #292
- Simplify otel filter directives by @andrewweston in #294
- Clarify wasi-otel comments by @andrewweston in #295
- Backend testing changes by @andrewweston in #296
- Orm to extension by @andrew-goldie in #297
- validate client-cert leaf certificate before use by @andrew-goldie in #298
- Propagate trace level between linked guests by @andrewweston in #299
- Simplify guest-to-guest context passing by @andrewweston in #300
- Fix missing trace color by @andrewweston in #301
- Set baggage in new guests by @andrewweston in #302
- Wasmtime 49 by @andrew-goldie in #303
- Repair release toolchain assumptions for 0.36.0 by @andrew-goldie in #304
Full Changelog: v0.35.0...v0.36.0
Release notes for previous releases can be found on the respective release branches of the repository.