This example demonstrates Chapter 6 of the UMA book: one contract, one core service, and two runtime targets that stay behaviorally aligned.
It is a reader-facing hands-on lab, not just a code dump.
The validated path proves that the same image.analyzed payload is emitted by both the native runner and the WASI runner while capability-gated native telemetry remains explicit, and a TypeScript reference implementation mirrors the shared portable analysis logic.
- one UMA contract drives both native and WASI execution
- shared logic lives in a single Rust crate and is reused without forks
- contract parameters change behavior without code edits
- parity is checked through emitted events, not by trusting the implementation
- Rust 1.77 or newer
rustup target add wasm32-wasip1- Wasmtime 20 or newer on your
PATH jqfor the shared-payload digest lab
- Validated path:
./scripts/smoke_portability_labs.sh - Main implementation: Rust workspace in
runtime/ - Secondary implementation: TypeScript reference analysis implementation in
ts/ - Reader labs:
./scripts/list_labs.shand./scripts/run_lab.sh - Shared payload parity check:
./scripts/lab_parity.sh - Rust/TypeScript reference parity check:
./scripts/compare_impls.sh
./scripts/list_labs.sh
./scripts/run_lab.sh lab1-native-wasm-parity
./scripts/run_lab.sh lab2-shared-payload-digest
./scripts/run_lab.sh lab3-failure-paths-and-capability-gates
./scripts/run_lab.sh lab4-rust-ts-reference-parity
./scripts/smoke_portability_labs.shExpected output signals:
Comparing shared image.analyzed events between native and WASM
Parity check passed: shared events are identical.
Native digest
WASM digest
Failure-path lab completed successfully.
Use this order if you just finished Chapter 6 and want the best learning path:
./scripts/list_labs.sh./scripts/run_lab.sh lab1-native-wasm-parity./scripts/run_lab.sh lab2-shared-payload-digest./scripts/run_lab.sh lab3-failure-paths-and-capability-gates./scripts/run_lab.sh lab4-rust-ts-reference-parity
Expected satisfaction point:
- by the end of lab 3, you should be able to explain what stayed portable, what remained target-specific, and why the contract still governed both targets
You should leave this lab able to explain:
- how one contract can drive both a native binary and a WASI binary
- why parity should be asserted from emitted events rather than assumed from shared code
- how target-specific capabilities such as GPU telemetry can stay explicit without contaminating the portable path
The most important signals are:
- the identical
image.analyzedevent payload emitted by both targets - the shared payload digests, which prove parity at the payload level
- the
gpu.telemetry.reportedevent, which shows the native-only capability boundary clearly
You got value from the Chapter 6 lab if you can explain all three of these points after running it:
- the portable behavior is proven by the shared
image.analyzedpayload, not by trusting the code structure - target-specific behavior is isolated in the native runner instead of leaking into the portable WASI path
- contract parameters in
CONTRACT.jsoncan change tagging outcomes without changing the shared Rust implementation
CONTRACT.json, Chapter 6 contract for the image analyzerruntime/, Rust workspace with the contract, bus, shared core logic, and both runnersts/, TypeScript reference implementation of the shared portable image-analysis logicsample-data/, example input imagesscripts/, parity, digest, failure-path, and smoke helpersdocs/, supporting notes such as the runtime sequence diagram
The contract includes:
parameters.tagging.avg_dark_thresholdparameters.tagging.avg_bright_threshold
Edit those values in CONTRACT.json and rerun ./scripts/run_lab.sh lab1-native-wasm-parity.
This is the cleanest way to see Chapter 6’s point that behavior can evolve through the contract without forking the service logic.
If you want the lower-level commands instead of the guided labs:
cd runtime
cargo test --locked --all
cargo build --locked -p runner_wasm --target wasm32-wasip1
cargo run --locked -p runner_native -- ../sample-data/sample.pgm
wasmtime run --dir=.. target/wasm32-wasip1/debug/runner_wasm.wasm ../sample-data/sample.pgmExample event:
{"event":"image.analyzed","payload":{"service":"uma.image-analyzer:1.0.0","path":"../sample-data/sample.pgm","tags":["high_contrast"],"metrics":{"width":8,"height":8,"avg":0.5,"contrast":1.0}}}See labs/README.md for the guided Chapter 6 lab notes.
Runs the native and WASI targets on the same input and compares their shared image.analyzed event line by line.
Builds on lab 1 and computes SHA-256 digests of the shared payloads so parity stays obvious even if you only care about the JSON payload itself.
Exercises malformed input rejection, WASI preopen enforcement, and the native GPU telemetry fallback event.
Compares the Rust shared analysis behavior against the TypeScript reference implementation for the built-in sample images.
| Concern | WASM runner | Native runner | Where to look |
|---|---|---|---|
| Contract loading | Yes | Yes | contract::Contract::load_from |
| File access | Via WASI preopens | OS file API | scripts/break_trust.sh |
| GPU access | No | Optional gpu feature |
runtime/crates/runner_native |
| Event schema check | Yes | Yes | bus::publish_validated |
| Deterministic analysis | Yes | Yes | core_service::analyze_image_data |
| Time source | None in portable path | Used for telemetry only | runner_native |
cargo test --locked --allverifies the workspacenpm test --prefix tsverifies the TypeScript reference implementation./scripts/smoke_portability_labs.shis the validated Chapter 6 smoke path./scripts/compare_impls.shchecks Rust/TypeScript parity for the shared analysis payloadruntime/tests/events.rschecks the native runner output shaperuntime/tests/smoke.rschecks that the shared contract is reachable
- If
./scripts/lab_parity.shsaysMissing required command: wasmtime, install Wasmtime or place a matching release under the repo.bin/directory. - If the WASM runner cannot read the sample image, make sure the
--dir=..preopen is present; the validated scripts already pass it. - If
./scripts/digest_shared.shsays artifacts are missing, rerun./scripts/run_lab.sh lab1-native-wasm-parity. - If you want to see the native GPU feature path with a real adapter, run
cargo run --locked -p runner_native --features gpu -- ../sample-data/sample.pgmfromruntime/.
- The bus validates event payloads against the JSON schema in
CONTRACT.json. - The WASI runner intentionally omits the native GPU telemetry path.
- The validated reader path is still Rust-first because Chapter 6 is fundamentally about native and WASI portability of the same Rust service. The TypeScript code is a reference translation of the shared analysis logic so readers can inspect the behavior in a second language without changing the chapter’s core point.
- Did the parity lab prove portability through observable output rather than assumption?
- Did the failure-path lab make the capability boundary between portable and native behavior obvious?
- Did the contract parameter experiment make behavior changes feel safer than code forks?
If this hands-on worked, you should finish it with three concrete gains:
- you can point to the exact emitted event that proves native and WASI stayed aligned
- you can explain why GPU telemetry belongs in the target-specific native path and not in the portable runner
- you can show how contract parameters let the behavior evolve without changing the shared service logic