Skip to content

Release v0.36.0

Choose a tag to compare

@github-actions github-actions released this 22 Sep 02:34
· 41 commits to main since this release
Immutable release. Only release title and notes can be modified.
4f57999

0.36.0

Released 2026-09-22

Added

  • The default wasi:http outbound backend validates the Client-Cert
    bundle's first certificate — the TLS identity presented to the server —
    before it is used: a leaf whose Extended Key Usage excludes clientAuth
    (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/loader host capability.
    A guest whose world imports it can ask the host to load a component at run
    time — load(package, location, digest?) returns a plain plugin record
    (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 → safe GuestArtifact::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 (refused for a wrong request or deployment,
    unavailable for a retryable acquisition failure, already-active for an
    identity conflict, internal for 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 through Plugins::install from the
    Wiring::extend hook; the runtime! macro's declarative
    plugins: { locations: [...] } list (or a manifest's [[location]]
    entries) carries them as deployment data (Location) that the
    generated hook installs through Plugins::install_declared. A load names
    its Origin — a registry endpoint or a location-relative path — and
    routes structurally by kind; an empty slot refuses typed. The built-in
    acquirers ship in omnia-plugin and re-export: PathMounts (named
    directory roots opened fail-fast at startup, read fresh on every load)
    and RegistryClient (exact namespace:name@version references from a
    declared default registry endpoint, verified against the registry's
    content digest, fresh-release-preferred when RegistryClient::cached
    attaches a store — ContentStore for digest-keyed bytes, ReleaseStore
    for per-registry release records). The loader
    links once on the shared linker when a deployment declares plugins;
    wasmtime wires it only
    into guests whose world imports omnia:plugins/loader. See
    docs/security-model.md.

  • The requester surface for the loader, in omnia-guest's new plugins
    module: a Plugins capability trait (WASI-backed default body on wasm32
    over the crate's own omnia:plugins/loader bindings, bare natively so
    suites script loads), the WasiPlugins zero-sized provider, shared
    PluginRef/Digest/Location types compiled on both targets (Digest
    validates and canonicalizes sha256:<hex> pins, with serde support), a
    Plugin handle carrying the routed identity and resolved digest, typed
    refusals mirroring the WIT error variant with kebab-case code()
    discriminants and a conversion into the guest Error taxonomy
    (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 to Backend::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 a wasm32-wasip2 fixture 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 = false still pulls nothing but std.

    • guest: a native double per omnia_guest capability trait —
      Scripted: Model, ScriptedLoader: Plugins (per-package digests and
      refusals, plus defaulting(digest) for every unscripted, unpinned load
      in place of the per-package placeholder), Memory: StateStore + BlobStore (with Namespaced; the BlobStoreExt operations derive from
      the primitives), MemoryDocs over the docstore default,
      ScriptedTables, MatchedHttp, Sink: Publish + Broadcast,
      MapConfig, FixedIdentity — plus guest::Provider, one struct
      implementing every capability trait over ten pub fields holding those
      doubles, each replaced through a consuming builder of the same name
      (Provider::default().config(..).model(..)), with StateStore and
      BlobStore sharing one storage field 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 a runtime! 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 generated Hooks
      (run_with). Backends bundles the twelve in-memory defaults, each
      swappable host a type parameter defaulting to its in-memory backend
      (Backends alone is the all-default bundle) with a consuming setter of
      the same name — model(m) for any WasiModelCtx, and keyvalue,
      blobstore, docstore, sql, vault, messaging, identity,
      otel for their Wasi*Ctx — so a production backend or a recording
      wrapper runs under the same guests the defaults do; the bundle
      implements Provides for 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 and SQLite connection (one private
      :memory: database per bundle) are built from fixed options.
      ScriptedModel scripts completions whose steps (tool calls and
      workspace read/write/list) run ahead of the answer, recording
      every exchange and the local_path lent per turn; an overrun answers
      the guest softly and fails the test at assert_exhausted(). Scratch
      gives per-test directories and mounts.
    • build, std-only: Components runs the nested cargo build a
      consumer's build.rs needs and writes gen.rs with one path constant
      per program and a foreach_<group>! completeness macro per group.
      Programs are [[example]] targets (examples, scan) or the shipped
      cdylib packages themselves (packages, scan_packages over a
      directory of crates — constants read <GROUP>_<NAME>, arms are the
      crate name), and extra_package builds 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 — with group naming the source added just before it;
      Program gains constant and example fields.
      Both scripted models share one Script core, so the same script reads
      identically at the handler and component rungs. cargo make ci now checks
      each feature alone and in combination (features, hack) and guards the
      build dependency tree (tree-guard).
  • The command façade, omnia_guest::api::command: the command-line mirror
    of api::http over the same Client, 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 at USAGE_EXIT (64, EX_USAGE, so exit
    2 always means a NotFound envelope). Command::new(&client, &metadata, format) is the projector: command.call(handler, decode, render).await -> Response runs 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 through Format::encode (with
    render as its text form) onto stdout at exit 0, and a decode or handler
    error (F::Error: Into<Failure>) becomes the Failure envelope on stderr
    at Error::exit_code() in the same Format; .hints(|error| ..)
    attaches a remedy hint to every failure that carries none. Failure wraps
    an omnia_guest::Error (From<Error>, From<anyhow::Error>) plus an
    optional with_hint(..), serializes flat as
    {"error","message","exit-code","hint"?}, and renders as text through
    Failure::text (error[<code>]: <message> then hint: <hint>).
    Response { stdout, stderr, exit } (success(stdout) /
    failure(stderr, exit)) buffers both channels and implements IntoExit:
    omnia_guest::command!(main) binds an async fn main() -> Response as
    the wasi:cli/run export (entries returning Result<(), u8> or () are
    accepted too), runs it through command::execute_wasi so telemetry is
    initialized and flushed, and writes the channels at that boundary — a
    BrokenPipe on either keeps the response's own exit, any other refused
    channel exits 3 (ServerError). completions::<App>(shell, name)
    produces a shell-completion script and clap_complete::Shell is
    re-exported. Metadata::from_env(prefix) is the command line's carrier
    for invocation metadata, reading <PREFIX>_REQUEST_ID /
    _CORRELATION_ID / _CAUSATION_ID through Metadata::from_lookup so 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 the clap::ValueEnum derive on
    api::Format for a --format text|json argument) ship behind the new
    omnia-guest command feature (clap + clap_complete); Command,
    Response, Failure, and command! need no feature. The e2e scenario
    programs enter through command! in place of the deleted
    test_programs::run!; because the export imports omnia:otel,
    omnia_test::host::Deployment::run_host links WasiOtel beside the host
    under test and requires B: 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 beside Error::status() with the
    1:1 exit map (BadRequest 1, NotFound 2, ServerError 3,
    BadGateway 4); Format { Text, Json } with
    Format::encode(&body, render) produces an Encoded { bytes, media_type }
    — text through a Fn(&T, &mut dyn fmt::Write) -> fmt::Result render
    closure as text/plain; charset=utf-8, or pretty JSON with a trailing
    newline as application/json — and Encoded implements axum's
    IntoResponse (200 with the media type as Content-Type).

  • omnia-guest http feature (default on): gates the axum-backed
    api::http routing, the mcp server, the HttpError / HttpResult
    root re-exports, and the hidden axum re-export behind
    dep:axum + dep:serde_urlencoded, so a command-only or messaging-only
    guest built with --no-default-features links no axum. The outbound
    HttpRequest capability, Error::status(), and api::messaging stay
    unconditional.

  • An e2e command group in crates/test-programs (command/exit_map, a
    command! guest on parse → Command::call over Metadata::from_env)
    driven by crates/omnia-test/tests/command.rs through
    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 exits USAGE_EXIT (64). test-programs' wasm32
    build enables omnia-guest's command feature and depends on clap.

  • omnia_wasi_websocket::WebSocketDefault::new() (and Default) is public:
    the backend without a listener, whose connect() still yields a client.
    omnia_wasi_http::ConnectOptions and omnia_wasi_sql::ConnectOptions are
    re-exported so connect_with can be called with fixed options instead of
    connect() reading HTTP_CONNECT_TIMEOUT / SQL_DATABASE.

  • omnia_wasi_otel::set_filter(directives): a guest replaces its tracing
    filter with a RUST_LOG string 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's RUST_LOG: valid environment directives are applied after them
    and win for the same target, invalid ones are reported to stderr and
    ignored. The subscriber init installs now holds its EnvFilter in 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 explicit RUST_LOG string instead of the environment
    (the always-on dependency mutes still apply).

  • Metadata over the dispatch chain. ChainCtx carries 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 at ChainPolicy::enter, so
    entries set after a dispatch never reach it. wasi-otel shows them to a
    guest as its baggage: omnia_wasi_otel::set_baggage(entries) (the new
    omnia:otel/baggage interface) 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::LEVEL is
    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 with set_baggage([(LEVEL, "info")]), beside the set_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
    valid RUST_LOG directives applied on top as before. A chain that names
    none opens the callee at error, as a root opens; an entry that is not a
    level is reported to stderr and read as error. 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 a ChainCtx (StoreConfig::chain, StoreBase::chain):
    Runtime::store() builds a server root, Runtime::store_in(chain) any
    other, and a StoreFactory takes the context its callee runs at, so the
    command driver, the trigger hosts, and call_fresh carry no ambient
    scope. The link relay snapshots the calling guest's context from its store
    through HasChain (implemented by StoreCtx beside HasMounts and
    HasExtensions), ChainPolicy::enter(&caller, &target) derives the
    callee's, and Dispatcher::invoke takes the caller's context ahead of
    the target. A host binding reaches the context through the same
    HasChain bound, as omnia:otel/baggage does.

Changed

  • Upgraded wasmtime and the matching wasmtime-wasi* crates to 49.0.0.
    wasmtime-wasi is linked with the p2 and p3 features only, so
    omnia::wasmtime_wasi::p1 no longer exists, and wasmtime-wasi-io is
    dropped from the workspace. Pre-compiled .bin artifacts must be rebuilt
    with omnia 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. The wasi:http server builds the guest
    request through upstream Request::from_http rather than a hand-mirrored
    copy of it (the + use<T> capture fix in 49 made the mirror unnecessary);
    behaviour is unchanged.

  • ChainCtx is Clone, not Copy, and no longer Default: it now
    carries the chain's metadata, and a root is ChainCtx::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::invoke takes the caller's ChainCtx before the target.
    Every guest that runs the telemetry lifecycle — an #[instrument]ed
    export, a command! entry — imports omnia:otel/baggage, since its
    subscriber opens at the level the chain carries, and so needs a runtime
    that carries it; WasiOtel does, as does the no-op OtelDefault bundle
    beneath it. A guest with no telemetry links unchanged, the componentizer
    having pruned the import.

  • The omnia-sdk orm feature is split into sql (the TableStore
    capability over wasi:sql) and docstore (the DocumentStore capability
    and the document_store re-exports), both default alongside http. A
    guest that disabled defaults and named orm now names the capability it
    uses; a default build is unchanged. In-repo consumers follow (examples:
    sql, docstore, http, command; omnia-test/guest: sql,
    docstore), and cargo make features gates each new feature alone.

  • Guest telemetry has one lifecycle entry point: omnia_wasi_otel::scope(f)
    initializes telemetry (once per instance), awaits the future f builds,
    and exports every buffered span and recorded metric before returning; a
    nested scope is a pass-through. #[instrument] on an async fn and
    command! both route through it, so the export completes ahead of an
    exit-with-code. init, flush_guard, and the ExitGuard drop guard
    are gone; flush() stays for a guest that exports mid-run. #[instrument]
    on a sync fn now 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 as correlation_id are 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 carry thread.id /
    thread.name attributes (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 nested scope.

  • 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 with deltatocumulative.

  • Guest telemetry no longer links the OpenTelemetry SDK. On wasm32,
    omnia-wasi-otel implements the opentelemetry API traits that the
    tracing-opentelemetry bridge and the global::tracer / global::meter
    registries drive (TracerProvider, Tracer, Span, MeterProvider,
    InstrumentProvider) directly over the omnia:otel records: a span
    becomes a span-data record as it ends, and the synchronous instruments
    aggregate into per-scope series that the flush collects into one
    resource-metrics record. Retained: the API and the bridge (a guest's
    opentelemetry and tracing code compiles unchanged), delta counters /
    histograms / gauges with cumulative up-down counters, the default
    histogram bounds and with_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 the opentelemetry_sdk=off mute leaves the guest filter
    with its target. The SDK and its rand / rand_chacha / rand_core
    tail leave every guest's dependency graph (trace and span ids are drawn
    from getrandom, over wasi:random); the release otel_metrics_counter
    guest shrinks from 1,085,552 to 865,767 bytes (−20%).

  • Host-side wasi:otel export fixes: each ScopeSpans.schema_url now
    carries the instrumentation scope's schema URL rather than the resource's
    (the metrics path already did); the exported metrics resource no longer
    gains a schema_url attribute (with a null value when unset) beside the
    ResourceMetrics.schema_url that already carries it; and guest spans
    dropped because no host span is live (the trigger hosts open theirs at
    DEBUG) are reported by one warn! per process instead of a debug! per
    export. Spans are grouped per scope by a linear scan over the handful an
    export carries; the hand-written Hash / Eq impls on the generated
    types (whose f64 arms broke the Eq contract 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_LOG alone, on every entry path, and an
    unset RUST_LOG now means warn rather than error, 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 at debug instead of warn. The
    direct-command host log flags (--debug / --quiet peeled from argv)
    and the LogMode presets 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::Components runs its nested wasm32-wasip2 build 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-hash OUT_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 sets CARGO_PROFILE_DEV_DEBUG=0: wasmtime
    reads no guest DWARF unless asked, so the fixtures carry none.

  • omnia-guest renamed to omnia-sdk: the crate lives at
    crates/omnia-sdk and guest code imports omnia_sdk::….
    omnia-guest-macros and the omnia-test guest feature 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::invoke now propagates dispatch depth into the callee
    and is bounded by GUEST_TIMEOUT_MS on server-rooted chains; a
    timed-out or cancelled caller aborts the callee. A 1 MiB string
    round trip full → 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
    tracing span 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
    config key) 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::Registry gains the optional
    config field and Location::registry_config(registry, config) beside
    Location::registry(registry); Plugins::install_declared parses the
    configuration when the runtime assembles, refusing malformed TOML or a
    default_registry that disagrees with registry. RegistryClient resolves
    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_config is 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_lookup mints a request id
    when the transport carries none — 32 lowercase hex chars from
    wasi:random on wasm32 (a std::hash::RandomState-derived id natively,
    for tests) — so HTTP (x-request-id), messaging metadata, and the command
    line's environment all yield a request_id, with the correlation id still
    falling back to it; Metadata::default() stays all-None. Client::call
    runs the handler inside a handler tracing span carrying request_id and
    correlation_id.

  • api::http::handle_with decoders return any error Into<HttpError>
    (D: Fn(RawRequest<'_>) -> Result<I, DE> with DE: Into<HttpError>)
    rather than only DecodeError, so an HTTP decoder classifies its refusal
    (a not_found! path parameter answers 404 with the JSON ErrorBody) as
    a command decoder already does. The JSON conveniences (get, post, …)
    keep DecodeError; a decoder closure that uses ? now names its return
    type (|raw: RawRequest<'_>| -> Result<Input, DecodeError> { .. }).

  • HttpError::from(omnia_guest::Error) now emits the JSON ErrorBody
    ({"error":"<code>","message":"<description>"}, application/json) at
    the variant's status instead of a plain-text code: …, description: …
    body, so HTTP clients read the same error discriminant every transport
    emits. This also covers From<DecodeError> (400 with
    error == "invalid_request") and the anyhow::Error conversion when its
    chain contains an omnia_guest::Error; a foreign anyhow error still
    produces a plain-text 500.

  • omnia_guest::api::command compiles on every target: only execute_wasi
    (the wasi:cli/run driver) and the command! macro that expands to it
    stay wasm32-only, so IntoExit, Command, Response, and Failure are
    testable natively.

  • omnia-guest defaults are now ["orm", "http"]. A guest that already
    disables default features and routes HTTP must add features = ["http"];
    a default build is unchanged.

  • The workspace-internal omnia-guest dependency entry carries
    default-features = false, following the omnia-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 the omnia-test dev-dependency unified orm and
    http back into every cargo clippy -p omnia-guest --no-default-features --all-targets line, so the features gate checks were vacuous; the
    http and command gates are now real (omnia-test/guest still carries
    orm because its doubles wrap the ORM traits). External consumers are
    unaffected: the crate's own default = ["orm", "http"] is unchanged.

  • The examples/cli guest (shared by cli-static) runs on the command
    façade: command!(main) over parse → Command::new(..).call(handler, decode, render), with a global --format text|json and
    Metadata::from_env("CLI"). Its hand-written CommandError is gone —
    handlers return omnia_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 the Failure envelope 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 and Copy: it owns
    Arc<str> / Arc<P> clones plus the Metadata, exposes owner() and
    provider() accessors over private fields (the public metadata field
    stays), and gains Context::new(owner, provider, metadata) so a handler
    can be unit-tested without a Client. Handler<P> implemented on the
    input type becomes Handler<P, I> with one blanket impl over every
    async fn(I, Context<P>) -> Result<O, E> (and every Clone closure of
    that shape, so a closure can carry configuration into a route); the
    trait method is call(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 returning Result<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::handler re-export are deleted — rustc's own diagnostics
    name a mis-shaped fn — and omnia-guest no longer depends on
    omnia-guest-macros, which now provides only #[instrument] (still
    reaching guests as omnia_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)
  • omnia is the composition root (deployment assembly, process
    lifecycle, optional-crate composition); omnia-core is the
    live-runtime SDK a capability crate targets; omnia-cli is a leaf
    grammar crate with no omnia-* dependencies. DeploymentBuilder is
    no longer typestate-parameterized: build is the safe wasm path and
    unsafe build_trusted replaces the Precompiled typestate's unsafe
    build (WasmOnly / Precompiled and precompiled() are deleted).
    omnia::run / run_with take a built Deployment (the
    runtime!-generated wrappers still take a DeploymentBuilder and
    build first); run_precompiled is deleted. Runtime::new /
    with_backends and their link/extend closures are deleted:
    Deployment::assemble builds a Runtime from RuntimeParts via
    Runtime::from_parts. Artifact loading is one GuestArtifact::load;
    Source decides trust by content (native bytes never reach wasmtime
    on the safe path), and Runtime::admit no longer sniffs ELF itself.
    Manifest, DeploymentBuilder, Deployment, Wiring, Backends,
    Mode, run / run_with, main, and compile live in omnia;
    omnia-core keeps the live handle (Runtime, Registry, admit,
    Extensions, dispatch, telemetry).

  • The run command-line grammar moves out of omnia-core into a new
    omnia-cli crate, which the omnia facade's cli feature now selects
    (cli = ["dep:omnia-cli"]). omnia-core no longer has a cli feature or
    a clap dependency; its entry point serves direct commands only.
    omnia_core::{Cli, Command, Parser} are now omnia_cli::{Cli, Command, Parser} — the facade paths omnia::Cli, omnia::Command, and
    omnia::Parser are unchanged, as is everything the runtime! macro emits.
    A direct-command binary built with --no-default-features now links no
    clap.

  • The omnia:plugins/loader capability (omnia-plugin) sits behind a new
    non-default plugin feature on the omnia facade
    (plugin = ["dep:omnia-plugin"]), mirroring cli. The facade paths
    (omnia::WasiPlugins, omnia::Plugins, omnia::PathMounts,
    omnia::LoadError, …) and the runtime! syntax are unchanged; a
    deployment that declares plugins: { locations: [...] }, or a bare
    plugins: {} beside config: for the TOML's [[location]] entries,
    must enable it (omnia = { version = "...", features = ["plugin"] }) or
    the expansion fails to compile naming omnia::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
    emits Wiring::extend only for those two shapes: an interfaces-only
    plugins: { interfaces: [...] } block no longer links a loader that
    refused every load, so a guest importing omnia:plugins/loader in such a
    deployment now fails at instantiation rather than per load; a bare
    plugins: {} without config: is now a compile error instead of a
    silent no-op. omnia::LoadError is owned by omnia-plugin rather than
    being the bindgen!-generated WIT error: same variants, Display, and
    Clone/Debug/PartialEq/Eq/Error, minus wasmtime's
    Lift/Lower/ComponentType impls.

  • Every omnia-guest capability trait is implemented for Arc<T>, &T,
    and Box<T> where T implements it, forwarding on both targets, so a
    provider field may hold a double behind a shared handle and a
    P: StateStore bound accepts Arc<Memory> with no wrapper struct.

  • BlobStore splits: the ten primitives (get, put, delete, list,
    get_range, object_info, create_container, delete_container,
    container_exists, container_info) stay on BlobStore; has,
    delete_objects, clear, copy_object, and move_object move to
    BlobStoreExt, a blanket-implemented extension trait whose native bodies
    compose the primitives and whose wasm32 bodies remain one host call
    each. Migration: delete those five methods from any hand-written
    BlobStore impl (they are no longer trait members) and import
    omnia_guest::BlobStoreExt where a call site uses them. Memory and
    Namespaced in omnia-test drop their overrides accordingly.

  • The omnia facade 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 as omnia::… instead of a wall of
    pub use omnia_core::… lines whose pages — and every path a reader copies
    from them — spelled the underlying crate; WasiPluginsCtxView joins the
    plugin re-exports so that surface is complete. The generated runtime
    module imports omnia::anyhow::Result rather than anyhow::Result, so
    runtime! no longer requires anyhow in the embedder's own Cargo.toml
    (omnia::anyhow is now a documented re-export, being the error vocabulary
    of Backend and Wiring, as is omnia::futures, whose BoxFuture the
    ContentStore/ReleaseStore/PathSource/RegistrySource seams return).
    omnia-plugin's docs no longer tell store
    implementors to depend on it directly — ContentStore/ReleaseStore and
    the Backend/NoOptions a cache: store also needs all reach embedders
    through omnia; depending on omnia-core or omnia-plugin is 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 by Runtime::admit from 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 through admit, whose
    atomic publication reports the loser via the new
    AdmitError::AlreadyRegistered variant, resolved against the winner's
    recorded digest (idempotent success on a match, already-active
    otherwise). sha256_digest moves to omnia-core (still re-exported from
    omnia).

  • Acquirers refuse honestly: RegistrySource and PathSource return the
    loader's typed LoadError — refused for an authoritative "no" (a
    malformed reference, a package or path the source does not serve),
    unavailable for 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 to link_interfaces(), and
    admission refusals name "link interfaces". The config surface is
    unchanged — the TOML plugins = [...] list, the macro plugins: block,
    and the CLI --plugins flag keep their names; only omnia-plugin speaks
    "plugin".

  • The omnia crate splits into omnia-core (the runtime spine: deployment,
    registry, dispatch, stores, telemetry, CLI) and a thin omnia facade that
    re-exports core, omnia-plugin, and the runtime! macro under the
    existing paths — embedder imports are unchanged. omnia-plugin is now the
    whole plugins capability: the loader WIT and WasiPlugins host binding,
    the Plugins load 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; typed AdmitError)
    and the Extensions typemap (capability state installed by the new
    Wiring::extend hook — replacing Wiring::acquirer — and read back from
    stores through HasExtensions; state that calls back into the runtime
    holds a WeakRuntime via Runtime::downgrade). Runtime::new's third
    parameter is now the extend hook (FnOnce(&Runtime<B>) -> Result<()>);
    StoreConfig::loader/StoreBase::loader are replaced by the extensions
    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 gains HostCtx (the host's borrow shape and view
    assembly, Borrow<'a> GAT + view), Provides<H> (the one
    bundle-accessor trait), and StoreView<H> (the one store-side view trait,
    blanketed on StoreCtx<B> for every B: Provides<H>). The per-host
    Wasi*View traits, Has* accessor traits, and per-host StoreCtx
    blankets are deleted; wasi_view! now emits the CtxView struct plus the
    host's HostCtx impl, and host add_to_linker accessors ride
    T: StoreView<WasiX> / T::view. The runtime! macro emits one uniform
    impl omnia::Provides<...> for Backends per hosts: row — no more name
    surgery from the host ident, so re-exports and third-party hosts wire the
    same way as first-party ones, and wasi:config's shared-borrow shape is
    carried by its own HostCtx impl 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::HttpCtx carrier (HasHttp is replaced by the backend-level
    HttpBorrow trait), and a dodged match now fails at compile time rather
    than silently. Hand-built bundles implement Provides<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 every hosts: 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 unread Server::IS_SERVER const are deleted; a third-party
    trigger host's run is now actually served instead of silently skipped.

  • The runtime! macro's plugins: key grows from a bracketed list to a
    block: plugins: { interfaces: [...], locations: [...] }. The
    declarative locations: list is the acquisition policy — named path roots
    and at most one registry endpoint — carried as manifest data
    (Manifest::locations, Location; [[location]] in omnia.toml)
    and installed by the generated Wiring::extend through
    Plugins::install_declared, which folds path entries into PathMounts
    and the registry entry into a cacheless RegistryClient, 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 empty locations: list and a second registry entry are
    spanned compile errors, and the bare-list form is a compile error naming
    the block shape. Declaring the block also links the omnia::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-carried cache: option is
    gone: a store-backed registry acquirer is installed by hand — Plugins::install over
    RegistryClient::new(..).cached(store) from a custom Wiring::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
    Hooks is now pub and generic — impl<B> Wiring<B> for Hooks where B: Provides<...> for each declared host — and two items join main and
    run in the re-export: manifest() returns the compiled-in
    ManifestSource, and async fn run_with<B>(builder, backends) drives a
    deployment through Hooks over a bundle already in hand, connecting
    nothing. Behind them, Wiring<B> is bounded on B: Clone + Send + Sync + 'static instead of Backends (a Backends bundle still satisfies it),
    omnia::run_with and Runtime::with_backends are the connect-free twins
    of run and Runtime::new, and Runtime::plugin_locations exposes the
    deployment's declared locations. omnia_test::host::Deployment builds on
    this as an overlay: Deployment::from(runtime::manifest()) takes the
    binary's manifest as its base, path_root rewrites the . location, and
    run_with::<runtime::Hooks, _>(backends) runs the production link,
    extend, and serve over omnia_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 dispatch to plugins:
    a top-level plugins = [...] in TOML, a top-level plugins: [...] key in
    the runtime! macro, the fluent Manifest::plugins(...) setter (with the
    Manifest.plugins field and the Manifest::link_interfaces() accessor),
    and the CLI flag --plugins (replacing --dispatch, no alias). A
    leftover top-level dispatch (or link), or plugins misplaced 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 the omnia:link WIT packages are untouched.

    # before                            # after
    dispatch = ["omnia:link/echo"]      plugins = ["omnia:link/echo"]
  • The host-mediated interface list is renamed from link to dispatch
    and is deployment-wide only: a top-level dispatch = [...] in TOML, a
    top-level dispatch: [...] key in the runtime! macro, the fluent
    Manifest::dispatch(...) setter, and the CLI flag --dispatch (replacing
    --link, no alias). The per-guest form (GuestEntry.link in 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 a dispatch misplaced 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. 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, a routes: { http: [...], ... } block per guest
    entry in the runtime! macro, GuestEntry::route_http /
    route_messaging / route_websocket programmatically), with the
    declaring guest as the implicit target. The top-level [[route.*]]
    tables, the macro's top-level routes: key, the Manifest::route_*
    setters, and the RouteSpec / HttpRoute / TopicRoute types 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's program: key: mode: command with 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 host run grammar. 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 the run
    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/run exporter, or to the guest entry marked command = true
    (macro command: 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, macro resolver:, DeploymentBuilder::resolver,
    Runtime::with_resolver), the http_paths trigger 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 (macro http_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
    and DeploymentBuilder::dynamic() are the way a registry grows after
    boot, with registered guests reachable via host-mediated link dispatch
    and Dispatcher::invoke

Removed

  • The guest ORM: the omnia_sdk::orm module (Entity, the Select /
    Insert / Update / Delete builders, Filter, Join, FetchValue)
    and the entity! macro, with the sea-query and chrono dependencies
    they carried. The layer now lives in omnia-orm
    (augentic/omnia-extensions),
    which builds on omnia_sdk::TableStore under the sql feature; the
    examples/sql guest executes parameterized SQL through TableStore
    directly.
  • The wrpc-transport / wrpc-wasmtime dependencies and the
    [patch.crates-io] git override that unpublished wrpc-wasmtime
    required. The omnia-core wrpc feature is gone, as are the
    omnia::{LinkClient, WrpcState, WrpcView, WrpcCtxView} and
    omnia_link::{LinkTransport, InProcess} types. LinkStore is
    relaxed to WasiView + '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, the deny.toml allow-git
    for bytecodealliance/wrpc, and the cargo vet git policies for
    wrpc-introspect / wrpc-transport.

What's Changed

Full Changelog: v0.35.0...v0.36.0


Release notes for previous releases can be found on the respective release branches of the repository.