Traverse v0.15.0 shipped on October 13. In v0.14.0 a signed exact-ref model package ran on native Rust and in the browser. In v0.15.0 the same package runs on the Swift, Kotlin/Android, and .NET embedders too, and every one of those hosts checks the package's rights before it runs. The loop is the one Traverse always had: discover → execute → trace. Find the pinned package, run exactly those bytes or refuse, and leave a record of what ran and under which rights.
+ +traverse-embedder-web@0.15.0, Swift swift-host-v0.15.0, Maven Central com.traverse-framework:traverse-embedder 0.15.0, and nuget.org TraverseEmbedder 0.15.0. Declaring model_usage is now required, and that is a breaking change. Core ML on Apple is opt-in. No host is Certified. The citeable matrix is which hosts run signed exact-ref models?
+ Three more hosts, one small boundary each
+ +Each new host gets a small, audited native boundary. None of them gets its own copy of the model path. Swift's ExactModelHost (Decision 104) runs packages on wasmi behind one C-ABI symbol, traverse_swift_host_model_call. Execution runs in fuel slices, so cancelling a Swift Task or hitting a deadline interrupts an inference that is already running. Kotlin/Android (Decision 108) runs on wasmi through the traverse-android-host JNI shim. The AAR ships arm64-v8a and x86_64 libraries, 16 KB page-aligned, and an emulator CI job runs the instrumented model tests against them. .NET (Decision 111) goes through two audited P/Invoke functions, and the NuGet package carries the native engine for win-x64, win-arm64, and linux-x64.
The part that matters is underneath. All three share one framed protocol crate, traverse-model-host-frame. They match the signed conformance vectors byte for byte, and they pass the same 21-case rights suite. "Every host" is a claim about behavior, not a row of logos. If a pinned package meant one thing in Swift and another in .NET, the pin would not be worth much.
Hosts still do UI and I/O. The model hands back raw values. Turning those into a label, a threshold, or a decision is business logic. It belongs in a capability with a contract, not once in a Kotlin view model and again in a Swift one. Where business logic lives is the longer version of that rule.
+ +Rights are checked, not just written down
+ +This is the breaking change. An app with exact_model_dependencies must declare model_usage: commercial or non_commercial. Leave it out and registration and execution fail closed with usage_undeclared. Rust sets ExactModelHostConnector::model_usage. Web passes modelUsage to ExactModelBrowserHost. Swift's ExactModelHost initializer takes modelUsage: with no default, so existing Swift call sites stop compiling until you add it. That is on purpose. A rights check you can forget to turn on is not much of a check.
A commercial usage, declared by the app or forced by the host, rejects any package whose rights say commercial_use: prohibited, with rights_policy_denied. Hosts also own a package-status map: a revoked package is blocked and a deprecated one is flagged. Every result carries model_evidence, the verified rights record, so the trace shows which rights the answer ran under. Earlier this month I wrote that model rights are data, not a README. v0.15.0 is the release where every host acts on that data.
One caveat, and it is a real one. The published web host does not validate the value yet. traverse-embedder-web@0.15.0 accepts strings other than commercial and non_commercial, so a typo such as "Commercial" quietly skips the commercial check. That is #1685. A fix is in review (#1698) and is not in a release. Native Rust uses an enum and cannot express a bad value. Until a release fixes the web host, pass exactly commercial or non_commercial, and test the commercial path once on purpose. The short page is what is model_usage?
Package your own ONNX model
+ +Before v0.15.0, getting a model into a signed package meant writing a guest yourself. Now traverse-cli model covers digest, sign, verify, pin, and conformance generate|check. model package-onnx turns an ONNX file into a Spec 138 package on an audited, import-free runner guest built on tract. The runner rebuilds byte for byte in CI. The digits-onnx-1.0.0 fixture scores 1727 of 1797 on every engine.
Watch the crate name. The CLI is published as traverse-cli-rs (the binary is traverse-cli). traverse-cli on crates.io is an unrelated tool. You need Rust 1.94 or newer:
cargo install traverse-cli-rs --version =0.15.0 --locked
+
+ The runner guest is not inside the crate. Download runner.wasm from the onnx-runner-v0.15.0 release, check its sha256 (f3902703fe79cc161c13b87cf507dcb25cb53ea3c2a68bed6cc753f9df100966), and pass it as the first argument to package-onnx. Then sign with a key you keep outside the repo, verify, generate a conformance vector, and pin. The walk-through is how do I package an ONNX model?
Two limits to know before you start. The packager handles one input tensor and one output tensor, so most Hugging Face text encoders, which take input_ids and attention_mask, do not fit yet (#1661). And model verify in traverse-cli-rs 0.15.0 cannot take a model usage; it always verifies as non_commercial. That is #1686, fixed on main after the release and not in 0.15.0.
Prepare once, run many
+ +Guest ABI v3 lets the host run model_prepare once, then restore a clean snapshot into a fresh instance on every call, bounded by a new max_snapshot_bytes ceiling. Output is byte-identical on the fresh path and the snapshot path, on every host. ONNX packages built by package-onnx prepare once. ABI v1 and v2 guests keep working, and packages signed for v0.14.0 do not need re-signing.
Core ML on Apple, if you ask for it
+ +A package can carry a signed Core ML variant. It runs only if the execution policy lists that placement and the variant passes an on-device self-check against its signed conformance vector, within tolerance. If it does not qualify, the call falls back down the ordered placements and the trace says so. If nothing qualifies, the call fails with placement_unavailable. Every result carries placement_evidence. The default placement is still wasm-cpu. The accelerator is a host detail. The package, the contract, and the trace stay the same.
Evidence so far is macOS test runs, where the signed digits-onnx-coreml package matches wasm-cpu within tolerance on every compute-unit setting, plus an earlier iPhone spike. Governed-path iPhone evidence is #1630, still open, so this post quotes no iPhone timings. There is no GPU adapter for web, Android, or desktop. More detail: can Traverse run models on a GPU or the Neural Engine?
What this release does not say
+ +-
+
- No host is Certified under Spec 529. Swift, Kotlin, and .NET are published and pre-1.0. +
digits-mlp-1.0.0is signed with a test-only key. v0.15.0 adds signing-key tooling (key generation, opt-inTRAVERSE_MODEL_SIGNING_KEYS, a dispatch-only signing workflow). Custody, rotation, and revocation of a production key are #1567, still open. See is production model signing ready?
+ - The host package-status map is not registry revocation. Runtime refusal of a revoked registry capability is #1598, not shipped. See what happens when a host marks a model package revoked? +
- This is the host connector path. It is not a catalog
model.executecapability, and it is not a Hugging Face integration.
+ - Python is still CLI-only. There is no Python SDK. +
Upgrading
+ +Read docs/upgrade-to-v0.15.0.md. Short version: declare model_usage on every host that runs pinned models. If you build ExecutionPolicy or HostConnectorHostResult with struct literals, or match every ModelFailureReason, add the new fields and variants. Pin the crates and traverse-embedder-web together at 0.15.0, with traverse-registry at 0.28.0. On Swift, Package.swift on main now pins the swift-host-v0.15.0-1 rebuild, which adds the Spec 138 guest reject codes (#1695).
The release notes and the v0.15.0 announcement are the source for everything above. If this post and those disagree, trust them, and open an issue.
+ +