1 ---
2 name: sdk-regressions
3 description: Cross-compilation regression checks for the onde crate. Covers macOS, iOS, tvOS (nightly), Android, Windows, and Linux target verification. Run before merging any change that touches platform-gated code, Cargo.toml, .cargo/config.toml, or build.rs. Also documents the onde-mistralrs fork publishing strategy and cargo publish workflow.
4 allowed-tools: Read, Edit, Glob, Grep, Terminal
5 user-invocable: true
6 ---
7
8 # SKILL: SDK Cross-Compilation Regression Checks
9
10 > Always run these checks before merging changes that touch `onde/src/`,
11 > `onde/Cargo.toml`, `onde/.cargo/config.toml`, `onde/build.rs`, or any
12 > platform-gated dependency.
13
14 ---
15
16 ## Why This Exists
17
18 The `onde` crate compiles for **6 target OSes** with heavy `#[cfg(target_os)]`
19 branching (106+ occurrences in `engine.rs` alone). A change that compiles on
20 macOS can silently break iOS, tvOS, or Android because:
21
22 - Different `mistralrs` feature sets per platform (`["metal"]` vs `[]`)
23 - Platform-conditional dependencies (`hf-hub` only on Android)
24 - Tier-3 targets (tvOS) require nightly + `-Z build-std`
25 - Assembly stubs (`tvos_chkstk.s`) and `build.rs` gating
26 - `.cargo/config.toml` sets `+fp16` rustflags on 5 Apple targets
27
28 See also: **[`cargo publish` workflow](#cargo-publish-workflow)** at the bottom
29 of this file — must be followed when releasing a new `onde` version.
30
31 ---
32
33 ## Tier 1 — Must Pass on Every PR (stable toolchain)
34
35 These use the default stable toolchain and must always succeed:
36
37 ```bash
38 # macOS — host platform, Metal backend
39 cargo check
40
41 # iOS device — Metal backend, +fp16
42 cargo check --target aarch64-apple-ios
43
44 # iOS simulator — Metal backend, +fp16
45 cargo check --target aarch64-apple-ios-sim
46 ```
47
48 ### Quick one-liner
49
50 ```bash
51 cargo check && \
52 cargo check --target aarch64-apple-ios && \
53 cargo check --target aarch64-apple-ios-sim
54 ```
55
56 ---
57
58 ## Tier 2 — tvOS (nightly toolchain, `-Z build-std`)
59
60 tvOS targets are tier-3 in Rust — they are **not** in `rustup target list` and
61 require nightly with `-Z build-std` and the `rust-src` component.
62
63 ```bash
64 # tvOS device
65 cargo +nightly check -Z build-std --target aarch64-apple-tvos
66
67 # tvOS simulator
68 cargo +nightly check -Z build-std --target aarch64-apple-tvos-sim
69 ```
70
71 ### tvOS-specific regression risks
72
73 | Risk | What breaks | How to detect |
74 |------|-------------|---------------|
75 | `scripts/tvos_chkstk.s` deleted | `Undefined symbol: ___chkstk_darwin` at link time | `test -f scripts/tvos_chkstk.s` |
76 | `cc` build-dep removed from `Cargo.toml` | `build.rs` can't compile the assembly stub | `grep 'cc' Cargo.toml` |
77 | `build.rs` tvOS block removed | Same linker error | `grep 'tvos' build.rs` |
78 | nightly `rust-src` not installed | `-Z build-std` fails immediately | `rustup +nightly component list --installed \| grep rust-src` |
79 | `.cargo/config.toml` `+fp16` removed | Silent precision loss or candle kernel panics | `grep 'fp16' .cargo/config.toml` |
80
81 ---
82
83 ## Tier 3 — Android, Windows, Linux (stable, may need cross-linker)
84
85 These check that CPU-only code paths compile. They may fail on linker resolution
86 if cross-linkers aren't installed, but `cargo check` (no linking) should pass:
87
88 ```bash
89 # Android arm64 — CPU, requires hf-hub sandbox workaround
90 cargo check --target aarch64-linux-android
91
92 # Windows — CPU
93 cargo check --target x86_64-pc-windows-msvc
94
95 # Linux — CPU
96 cargo check --target x86_64-unknown-linux-gnu
97 ```
98
99 ---
100
101 ## Full Regression Suite (copy-paste)
102
103 ```bash
104 #!/usr/bin/env bash
105 set -euo pipefail
106
107 echo "═══ Tier 1: macOS + iOS (stable) ═══"
108 cargo check
109 cargo check --target aarch64-apple-ios
110 cargo check --target aarch64-apple-ios-sim
111
112 echo "═══ Tier 2: tvOS (nightly + build-std) ═══"
113 cargo +nightly check -Z build-std --target aarch64-apple-tvos
114 cargo +nightly check -Z build-std --target aarch64-apple-tvos-sim
115
116 echo "═══ Tier 3: Android / Windows / Linux ═══"
117 cargo check --target aarch64-linux-android
118 cargo check --target x86_64-pc-windows-msvc
119 cargo check --target x86_64-unknown-linux-gnu
120
121 echo "✅ All cross-compilation checks passed"
122 ```
123
124 ---
125
126 ## Required Rustup Targets
127
128 Install these once on a development machine:
129
130 ```bash
131 # Tier 1 (stable)
132 rustup target add aarch64-apple-darwin x86_64-apple-darwin
133 rustup target add aarch64-apple-ios aarch64-apple-ios-sim
134
135 # Tier 3 (stable)
136 rustup target add aarch64-linux-android armv7-linux-androideabi
137 rustup target add x86_64-linux-android i686-linux-android
138 rustup target add x86_64-pc-windows-msvc
139 rustup target add x86_64-unknown-linux-gnu
140
141 # Tier 2 — tvOS (nightly, no `rustup target add` — uses -Z build-std)
142 rustup toolchain install nightly
143 rustup +nightly component add rust-src
144 ```
145
146 ---
147
148 ## `.cargo/config.toml` — Critical Settings
149
150 These rustflags are set at the workspace level and **must not be removed**:
151
152 ```toml
153 [target.aarch64-apple-ios]
154 rustflags = ["-C", "target-feature=+fp16"]
155
156 [target.aarch64-apple-ios-sim]
157 rustflags = ["-C", "target-feature=+fp16"]
158
159 [target.aarch64-apple-tvos]
160 rustflags = ["-C", "target-feature=+fp16"]
161
162 [target.aarch64-apple-tvos-sim]
163 rustflags = ["-C", "target-feature=+fp16"]
164
165 [target.x86_64-apple-tvos]
166 rustflags = ["-C", "target-feature=+fp16"]
167 ```
168
169 Removing `+fp16` causes silent precision degradation or runtime panics in
170 candle's Metal/NEON kernels.
171
172 ---
173
174 ## Platform Dependency Matrix
175
176 | `target_os` | `mistralrs` features | Extra deps | GPU | Default model |
177 |-------------|---------------------|------------|-----|---------------|
178 | `macos` | `["metal"]` | — | Metal | Qwen 2.5 3B |
179 | `ios` | `["metal"]` | `hf-hub`, `mistralrs-core` | Metal | Qwen 2.5 1.5B |
180 | `tvos` | `["metal"]` | `hf-hub`, `mistralrs-core` | Metal | Qwen 2.5 1.5B |
181 | `android` | `[]` | `hf-hub`, `mistralrs-core` | CPU | Qwen 2.5 1.5B |
182 | `windows` | `[]` | — | CPU | Qwen 2.5 3B |
183 | `linux` | `[]` | — | CPU | Qwen 2.5 3B |
184
185 `hf-hub` and `mistralrs-core` are required on all **sandboxed** platforms
186 (iOS, tvOS, Android) for the `GLOBAL_HF_CACHE` workaround — `~/.cache` is
187 outside the app container on iOS/tvOS, and `dirs::home_dir()` panics on Android.
188 The re-exports in `src/lib.rs` (`pub use hf_hub; pub use mistralrs_core;`) are
189 gated to `#[cfg(any(target_os = "android", target_os = "ios", target_os = "tvos"))]`.
190
191 ---
192
193 ## Common Regression Patterns
194
195 ### 1. New model added but not to all 6 platform `cfg` blocks
196
197 `engine.rs` has separate `#[cfg(target_os = "...")]` blocks for model builder
198 logic. A new model constructor added only under `macos` will fail on `ios`.
199
200 **Detection:** Tier 1 checks catch iOS; Tier 2 catches tvOS; Tier 3 catches
201 Android/Windows/Linux.
202
203 ### 2. Dependency added without platform gating
204
205 Adding a dep that uses `SystemConfiguration.framework` (e.g., `hyper_util`
206 proxy detection) works on macOS but causes linker errors on iOS/tvOS unless the
207 podspec declares `s.frameworks = 'SystemConfiguration'`.
208
209 **Detection:** Tier 1 `cargo check` passes (no linking), but `flutter build ios`
210 fails. Must also test a full `flutter build` periodically.
211
212 ### 3. Fork branch divergence
213
214 All `mistralrs` deps point to a fork:
215 `git = "https://github.com/setoelkahfi/mistral.rs"`, branch
216 `fix/all-platform-fixes`. If that branch is rebased, deleted, or diverges from
217 upstream, **all** platforms break.
218
219 **Detection:** Any `cargo check` fails with "could not find branch".
220
221 ### 4. UniFFI version mismatch
222
223 `uniffi = "=0.31.0"` must be pinned identically in three places:
224 - `onde/Cargo.toml` dependencies
225 - `onde/Cargo.toml` build-dependencies
226 - `onde/uniffi-bindgen/Cargo.toml`
227
228 Mixing versions causes bindgen panics at codegen time.
229
230 **Detection:** `cargo build` in `uniffi-bindgen/` or `build.rs` panics.
231
232 ### 5. Android `home_dir()` sandbox panic
233
234 `dirs::home_dir()` panics in the Android sandbox. The `hf-hub` dep is added
235 explicitly on Android so `HF_HOME` can be set programmatically. Never call
236 `dirs::home_dir()` or `home::home_dir()` in code paths reachable on Android.
237
238 **Detection:** Tier 3 `cargo check --target aarch64-linux-android` catches
239 compile-time issues; runtime panics need an Android emulator test.
240
241 ---
242
243 ## Dart SDK Cross-Build Checks
244
245 When changes touch `sdk/dart/`, also verify the Flutter plugin builds:
246
247 ```bash
248 cd sdk/dart/example
249
250 # macOS (requires Xcode)
251 flutter build macos --debug
252
253 # iOS (requires Xcode + iOS SDK)
254 flutter build ios --debug --no-codesign
255 ```
256
257 ### Known environment pitfall
258
259 **Never export Android NDK toolchain binaries globally in `.zshrc`:**
260
261 ```bash
262 # ❌ BREAKS Xcode builds — NDK's ld/ar/strip shadow Apple's tools
263 export LD=$TOOLCHAIN/bin/ld
264 export AR=$TOOLCHAIN/bin/llvm-ar
265
266 # ✅ Safe — env vars only, no PATH/tool overrides
267 export ANDROID_NDK_HOME="$HOME/Library/Android/sdk/ndk/29.0.14206865"
268 ```
269
270 If Xcode invokes the NDK's `ld` instead of Apple's, you get:
271 `ld: error: unknown argument '-Xlinker'`
272
273 The fix: comment out the tool exports, use Cargo's
274 `sdk/dart/rust/.cargo/config.toml` for Android linker configuration instead.
275
276 ---
277
278 ## Cache Path Consistency (Cross-SDK)
279
280 All Onde-powered apps share downloaded models via the App Group
281 `group.com.ondeinference.apps`. The cache layout inside the shared container
282 **must be identical** across every SDK and app type:
283
284 ```
285 <group container>/
286 ├── models/ ← HF_HOME
287 │ └── hub/ ← HF_HUB_CACHE (GGUF files live here)
288 └── tmp/ ← TMPDIR (iOS sandbox restricts system TMPDIR)
289 ```
290
291 **The subdirectory is `models/`, NOT `huggingface/`.**
292
293 Files that set this path (all must agree):
294
295 | File | Variable | Expected value |
296 |------|----------|----------------|
297 | `onde/sdk/dart/rust/src/api.rs` (`configure_cache_dir`) | `hf_home` | `data_dir.join("models")` |
298 | `onde/src/hf_cache.rs` (`download_model` — general) | `hf_home` | `data_dir.join("models")` |
299 | `onde/src/hf_cache.rs` (`download_model` — Android) | `hf_home` | `resolved_app_data.join("models")` |
300 | Tauri apps (`setup_application_filesystem.rs`) | `models_home` | `container_dir.join("models")` |
301 | `onde/src/hf_cache.rs` (`hf_cache_dir` — fallback) | default | `~/.cache/huggingface/hub` (non-sandboxed only, OK to differ) |
302
303 **Regression check:** `grep -rn 'join("huggingface")' src/hf_cache.rs sdk/dart/rust/src/api.rs`
304 should return **only** the non-sandboxed fallback in `hf_cache_dir()` (line ~164).
305 Any other match means a sandboxed path is using the wrong subdirectory.
306
307 ---
308
309 ---
310
311 ## Publishing to crates.io
312
313 ### Why we don't use upstream `mistralrs` directly
314
315 The short version: our PRs aren't merged yet.
316
317 `onde` depends on a personal fork of Eric Buehler's `mistral.rs`, kept at
318 `github.com/setoelkahfi/mistral.rs` on the `fix/all-platform-fixes` branch.
319 The fork is ahead of upstream by ~41 commits carrying fixes that only matter
320 for Apple and Android targets — things upstream hasn't needed to care about:
321
322 | Fix | Status |
323 |-----|--------|
324 | iOS Metal 3.0 support | PR open |
325 | Android 32-bit memory limit constants | PR open |
326 | HF_HOME propagation in diffusion/flux pipelines | PR open |
327 | `metallib` link step `--sdk` / `-std` flags | PR open |
328
329 Dropping the fork and pointing at upstream `mistralrs 0.8.1` would silently
330 break iOS on entry-level devices and Android on 32-bit targets. Don't do it
331 until those PRs land.
332
333 **Legal note:** The fork is a derivative of Eric Buehler's MIT-licensed
334 `mistral.rs`. His `LICENSE` file — including `Copyright (c) 2024 Eric Buehler`
335 — must remain intact and unmodified in the fork at all times. See
336 [`legal-and-trademarks/SKILL.md`](../legal-and-trademarks/SKILL.md) for full
337 attribution obligations.
338
339 ### The `onde-mistralrs` workaround
340
341 `cargo publish` strips every `git =` field before uploading to crates.io.
342 That means a git-only dep is useless to anyone who installs `onde` from the
343 registry — they can't resolve it.
344
345 The solution: publish the fork to crates.io under the name `onde-mistralrs`
346 (and `onde-mistralrs-core`, etc.), then reference it with `package =` so the
347 rest of the code still compiles unchanged with `use mistralrs::...`:
348
349 ```toml
350 # In onde/Cargo.toml — each platform target section looks like this.
351 # Cargo resolves it as "onde-mistralrs" from crates.io,
352 # but the Rust code sees it as `mistralrs`.
353 mistralrs = { version = "0.8.2", package = "onde-mistralrs", features = ["metal"] }
354 ```
355
356 This is the only supported publish path until the upstream PRs merge. Don't
357 swap it for plain `mistralrs` from crates.io without checking every platform
358 target first.
359
360 ---
361
362 ### Step 1 — check if `onde-mistralrs` needs a new publish
363
364 First, see how far the fork has drifted from upstream:
365
366 ```bash
367 curl -s "https://api.github.com/repos/setoelkahfi/mistral.rs/compare/EricLBuehler:mistral.rs:master...setoelkahfi:mistral.rs:fix%2Fall-platform-fixes" \
368 | python3 -c "import sys,json; d=json.load(sys.stdin); print('ahead:', d['ahead_by'], 'behind:', d['behind_by'])"
369 ```
370
371 If there are new commits since the last `onde-mistralrs` publish, or if
372 upstream cut a new release that the fork has rebased onto, bump the workspace
373 version in the fork and publish. Sub-crates must go in dependency order:
374
375 ```bash
376 # 1. Edit the root Cargo.toml in setoelkahfi/mistral.rs
377 # bump version, e.g. 0.8.1 → 0.8.2
378
379 # 2. Publish in order — each one depends on the previous:
380 cargo publish -p onde-mistralrs-macros
381 cargo publish -p onde-mistralrs-paged-attn
382 cargo publish -p onde-mistralrs-quant
383 cargo publish -p onde-mistralrs-core
384 cargo publish -p onde-mistralrs
385
386 # 3. Give the index ~30 s to propagate before moving on.
387 ```
388
389 ---
390
391 ### Step 2 — update `onde/Cargo.toml`
392
393 Every platform target section has its own `onde-mistralrs` line. They all need
394 to move to the new version together — leaving one target on an older version
395 causes a dependency conflict.
396
397 ```bash
398 # All occurrences should print the same version number:
399 grep 'onde-mistralrs' onde/Cargo.toml
400 ```
401
402 ---
403
404 ### Step 3 — dry-run first
405
406 ```bash
407 cd onde
408 cargo publish --dry-run --allow-dirty 2>&1 | tail -20
409 ```
410
411 A clean run ends with `Finished` and no errors. Common things that go wrong:
412
413 | Error | What to do |
414 |-------|------------|
415 | `all dependencies must have a version` | add `version = "..."` to the dep |
416 | `no matching package named onde-mistralrs` | publish the fork first (step 1) |
417 | `git specification will be removed` | expected — cargo is just telling you the git field gets stripped |
418 | `files in working directory contain changes` | add `--allow-dirty` |
419
420 ---
421
422 ### Step 4 — publish `onde`, then update `sigit`
423
424 ```bash
425 cargo publish
426 ```
427
428 Then bump the version in `sigit/Cargo.toml`:
429
430 ```toml
431 # git = is used for local dev; crates.io consumers resolve via version =
432 onde = { version = "0.1.9", git = "https://github.com/ondeinference/onde", branch = "development" }
433 ```
434
435 Keep both fields. The `git =` field takes precedence during local development;
436 crates.io strips it and falls back to `version =` for anyone installing from
437 the registry.
438
439 ---
440
441 ### GresIQ credentials and `HF_TOKEN`
442
443 People building `onde` from crates.io don't need to do anything about secrets.
444 Here's why.
445
446 `build.rs` passes credentials to the compiler via `option_env!()`. That macro
447 resolves at compile time — if the env var isn't set, it compiles to `None` and
448 the feature that needs it is silently disabled. No crash, no partial state, no
449 mystery error at runtime.
450
451 ```rust
452 // pulse/client.rs — GresIQ telemetry (Onde Inference internal infrastructure)
453 const EMBEDDED_API_KEY_DEV: Option<&str> = option_env!("GRESIQ_API_KEY_DEV");
454 const EMBEDDED_API_SECRET_DEV: Option<&str> = option_env!("GRESIQ_API_SECRET_DEV");
455
456 // token.rs — HuggingFace Hub authentication
457 const BUILD_TIME_HF_TOKEN: Option<&str> = option_env!("HF_TOKEN");
458 ```
459
460 Who gets what:
461
462 | Who is building | Env vars present? | What happens |
463 |-----------------|-------------------|--------------|
464 | Our CI (official builds) | yes, via GitHub secrets | telemetry on; HF token baked into binary |
465 | Someone who ran `cargo add onde` | no | telemetry off; HF token read from `~/.cache/huggingface/token` at runtime |
466 | iOS/tvOS builds via XCFramework | yes, via `.env` file | HF token baked in — required because there's no writable filesystem on device |
467
468 The GresIQ credentials are Onde Inference's own infrastructure secrets — they
469 exist so the SDK can phone home for usage telemetry on official builds. External
470 consumers don't have them, don't need them, and won't be affected by not having
471 them.
472
473 Never put `GRESIQ_API_KEY_*` or `HF_TOKEN` in `README.md`, `.env.example`, or
474 any committed file. The `.env` in the crate root is git-ignored for a reason:
475
476 ```bash
477 # onde/.env — git-ignored, never commit
478 GRESIQ_API_KEY_DEV=...
479 GRESIQ_API_SECRET_DEV=...
480 GRESIQ_API_KEY_PRODUCTION=...
481 GRESIQ_API_SECRET_PRODUCTION=...
482 HF_TOKEN=hf_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
483 ```
484
485 ---
486
487 ### Working against the git fork locally
488
489 If you're making changes to the fork and want `onde` to pick them up without
490 publishing, uncomment the patch block in `onde/Cargo.toml`:
491
492 ```toml
493 [patch.crates-io]
494 onde-mistralrs = { git = "https://github.com/setoelkahfi/mistral.rs", branch = "fix/all-platform-fixes", package = "mistralrs" }
495 onde-mistralrs-core = { git = "https://github.com/setoelkahfi/mistral.rs", branch = "fix/all-platform-fixes", package = "mistralrs-core" }
496 ```
497
498 Re-comment it before running `cargo publish`. Cargo strips `[patch.crates-io]`
499 from the published manifest automatically, so it won't affect registry
500 consumers either way — but leaving it active makes the dry-run confusing.
501
502 ---
503
504 ### Pre-publish checklist
505
506 - [ ] all `onde-mistralrs` version numbers in `onde/Cargo.toml` are the same
507 - [ ] `onde/Cargo.toml` `[package] version` is bumped
508 - [ ] `sigit/Cargo.toml` `onde` version matches
509 - [ ] `[patch.crates-io]` git override is commented out
510 - [ ] `cargo publish --dry-run --allow-dirty` passes clean
511 - [ ] `.env` is not listed in the dry-run's packaged file output
512 - [ ] Eric Buehler's `LICENSE` in the `mistral.rs` fork is unmodified
513
514 ---
515
516 *Last updated: July 2025 — added hf-hub/mistralrs-core iOS/tvOS deps,
517 cache path consistency checks, App Group convention, onde-mistralrs publish
518 strategy, GresIQ/HF_TOKEN secrets documentation.*
519