# Running apps You don't need an Ark to write an app. A WebAssembly runtime and the fixture data in this repository run both passes on a laptop. When the app works, the `ark` command line tool runs it on an Ark, whether hardware or emulated. ## Locally ### Toolchains All of these ship for macOS, Linux and Windows. The `make` targets and `tools/run.sh` need a POSIX shell, so build from WSL on Windows. - [`wasmtime`](https://wasmtime.dev/) runs the modules. - [Binaryen](https://github.com/WebAssembly/binaryen/releases) supplies `wasm-opt`, which every build runs over its module. - Rust needs `rustup` with the `wasm32-wasip1` target. - Go builds with [TinyGo](https://tinygo.org/getting-started/install/), which emits a much smaller module than the standard toolchain. - C and Python need a [wasi-sdk](https://github.com/WebAssembly/wasi-sdk/releases) release. The build looks in `/opt/wasi-sdk`, and `WASI_SDK` points it wherever you unpacked it. C alone also accepts any clang with a WASI sysroot, such as Homebrew's `llvm`, `lld`, `wasi-libc` and `wasi-runtimes`, which the build falls back to. - Python also needs `make` and a [CPython](https://www.python.org/downloads/) of 3.13 or newer. Set `PYTHON` when the one to build with is not `python3`. Package managers cover everything except wasi-sdk, which is a tarball to unpack. With Homebrew that is: ```sh brew install wasmtime binaryen tinygo python@3.14 rustup target add wasm32-wasip1 ``` C apps build with a WASI toolchain rather than Emscripten, because Emscripten's standalone modules can't open the files the Ark mounts. These examples are built and measured with wasmtime 48.0.2, Binaryen 132, Rust 1.98.0, TinyGo 0.42.0, wasi-sdk 33.0 and CPython 3.14.7. Newer versions usually work. The Python build takes its version from the interpreter you run it with, because CPython can only be cross compiled by its own release series. It downloads that exact source into `build/python`, builds one shared runtime from it and freezes each app's imports, linking the native extensions they need. The first build takes a few minutes, and every later Python app reuses the runtime. Dynamic imports go in `--include` when calling `tools/python_build.py` directly. ### Building and running ```sh make build # build every app into build/.wasm make run # build and run every app make run APP=04-cilantro-mini-rust make run APP=04-cilantro-mini-rust FIXTURES=fixtures/no-call ``` `make run` does what an Ark does, through `tools/run.sh`. It runs the module with no arguments to collect its manifest, mounts only the declared datasets from the fixture root, read-only, and runs the module again with `/` as its first argument. An undeclared path is never mounted, which is how [02-permissions](../apps/02-permissions) can show a blocked read on a laptop. An optional path is mounted when the fixture root holds it, as if the owner switched it on, and left out when the root lacks it, the way an Ark leaves out data it doesn't have. `OPTIONAL=off` leaves every optional path out, as if the owner declined them all: ```sh OPTIONAL=off make run APP=02-permissions ``` An app that declares inputs gets its answers from files in its folder's `inputs/` directory, one per input, which `make run` mounts at `/inputs`. `INPUTS` points it at another directory of answer files. The answers are taken as given, so a local run never checks them against their declarations. ```sh INPUTS=my-answers make run APP=03-sundae-order ``` An app kept outside this repository runs against these fixtures the same way, since `tools/run.sh` takes any module, as in `sh tools/run.sh path/to/app.wasm fixtures`, with a directory of answers as a third argument when the app declares inputs. Build it with the `wasm-opt` feature flags in `tools/build.sh`, because an Ark's runtime accepts only those. ### Small modules An Ark uploads and starts a smaller module faster, so every build here is tuned for size. Rust uses `opt-level = "z"`, fat link-time optimization, a single codegen unit and stripped symbols, all set in each app's `Cargo.toml`. Go builds with TinyGo at `-opt=z`. C builds with `-Oz` and drops unused sections. Python builds a trimmed interpreter carrying only the extensions the app imports. Every module then goes through `wasm-opt`. The language decides most of it. The C, Go and Rust modules here are tens to a few hundred kilobytes, while a Python one runs to megabytes because it carries the interpreter, and it starts noticeably slower on an Ark. Reach for Python when the libraries or the clarity are worth that, and for the others when startup matters. ### Fixtures The fixtures are small plain-text stand-ins for what an Ark mounts. The default root, `fixtures`, models one invented genome on GRCh38. Its coordinates, reference alleles and sequences are public data, while every genotype is made up. Two more roots cover the answers an app must handle: - `fixtures/no-call` answers every panel variant with a no-call, `./.`. - `fixtures/unanswered` has coordinates and reference alleles but no genotypes, like reference sites in a variants-only call file. Both hold only the variant data the rsID apps need, so other apps stop at their missing grants there. ### What a laptop doesn't reproduce - **The checks before approval.** Locally nothing refuses a start section, a bad name, a path that can't be granted or isn't on the Ark, or an answer that doesn't fit its input. - **The limits.** Memory, output and the manifest pass's time are unbounded. - **The deterministic sandbox.** A stock `wasmtime` gives real randomness and real clocks, so don't depend on either, as [10-fortune-cookie](../apps/10-fortune-cookie) explains. - **The owner's review.** You always see standard output and standard error at once, as if `develop` were set and the owner released every report. - **The generated tree.** Plain directories list everything, and no read fails with an I/O error or "file too large". - **Startup cost.** A laptop starts a module far faster than an Ark does, so a heavy module feels cheaper here than it is there. An emulated Ark has all of these except the startup cost, since it runs the real firmware at your computer's speed. ## On an Ark Install the [`ark`](https://github.com/dark-bio/cli) command line tool as its README describes. It talks to an Ark plugged in over USB, or to one that [Ark Emulator](https://github.com/dark-bio/emulator) boots on your computer from the real firmware. The emulator keeps its data in a plain file, and `ark-emulator start` boots one. An Ark is paired with `ark pair` once, and unlocked with `ark unlock` after each power loss, both approved on the owner's phone in Ark Companion. A fresh emulator is first enrolled at Ark Hub, in a browser, at the address `ark enroll` prints. Then check what the Ark holds and run the app: ```sh ark status # trust, firmware, pairing and lock state ark data paths # the paths apps can read, and what is available ark run build/04-cilantro-mini-rust.wasm > report.md ``` `ark run` uploads the module and waits while the owner approves it on their phone, with any optional grants and answers. When the app ends, the owner reviews its report, and the command writes it to standard output once the owner releases it. A refused app comes back with the reason, and a report the owner keeps fails the command. `ark help apps` covers manifests and grants, and `ark help datasets` covers the data commands. AI agents read `ark help agents` first, and `ark-emulator help agents` for the emulator.