From d18bfae5f01705a5c82985ca15503e813df7e4db Mon Sep 17 00:00:00 2001 From: IsuraManchanayake Date: Fri, 23 Oct 2026 13:59:40 +1100 Subject: [PATCH 1/3] basert: with no --version, install the newest release this build drives runtime install basert answered from the pins first, so a fresh install took 0.3.0, the newest pinned engine, and 0.4.0 was refused without --sha256, although 0.1.3's changelog says the newest release with this build's ABI number is taken. With no version named, the release listing is read first and the newest driven release wins, pinned or not, checked against the SHA-256 GitHub lists for its engine (the digest baseRT also publishes as .sha256). The pins answer offline, and 0.4.0 joins them. Pre-releases are never the newest. --- CHANGELOG.md | 1 + .../superfluid-adapter-basert/recipe.json | 18 ++++- .../superfluid-adapter-basert/src/recipe.rs | 69 +++++++++++++++---- docs/models/runtimes.md | 2 +- docs/release-notes/0.1.4.md | 7 ++ 5 files changed, 82 insertions(+), 15 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index c3f0c1b..8ffa928 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,7 @@ ## 0.1.4 - `superfluid serve` takes `--pull-file ` for a baseRT model: one `.base` from a repo that holds several, such as one whose files are named by size (`Foo-Medium.base`) rather than by quant. It is passed to `basert pull --file`, and the model is served from the install `basert list --json` records that file in. It needs basert 0.4.1 or later. +- `superfluid runtime install basert`, and a `serve` that installs it first, take the newest baseRT release this build drives, as 0.1.3 said they did: it installed the newest pinned one (0.3.0), and a newer release only with `--sha256`. The engine's digest is the one GitHub lists for it; the pins, now with 0.4.0, answer offline. ## 0.1.3 diff --git a/crates/superfluid-adapters/superfluid-adapter-basert/recipe.json b/crates/superfluid-adapters/superfluid-adapter-basert/recipe.json index eb2fe2b..e0132f4 100644 --- a/crates/superfluid-adapters/superfluid-adapter-basert/recipe.json +++ b/crates/superfluid-adapters/superfluid-adapter-basert/recipe.json @@ -1,8 +1,24 @@ { - "about": "baseRT engines this adapter installs without being told a SHA-256, as basecompute publishes them: each engine with the size and SHA-256 its release lists (the digest GitHub shows and the release's own .sha256 agree). A version not here installs only with --sha256. Add a release here, newest first, after checking both digests.", + "about": "baseRT engines this adapter installs with no release listing, as basecompute publishes them: each engine with the size and SHA-256 its release lists (the digest GitHub shows and the release's own .sha256 agree). With the listing, the newest release whose engine ABI this build drives is installed, by the digest GitHub lists for its engine; these pins answer offline and for `runtime versions`. Add a release here, newest first, after checking both digests.", "release": "https://github.com/basecompute/baseRT/releases/download/v{version}/{name}", "releases": "https://api.github.com/repos/basecompute/baseRT/releases?per_page=50", "pinned": [ + { + "version": "0.4.0", + "abi": 1, + "assets": [ + { + "name": "basert-engine-linux-arm64-cuda-0.4.0.tar.gz", + "size": 36349756, + "sha256": "8b06deb5a51e03d6327f1cd0149abf6b19f3aefa74758afceba59eb289bdb5c2" + }, + { + "name": "basert-engine-macos-arm64-0.4.0.tar.gz", + "size": 20249930, + "sha256": "c9b49ea1906fa871c646df3f62fb8237b53a817f786356323ff04bda1e6e79f2" + } + ] + }, { "version": "0.3.0", "assets": [ diff --git a/crates/superfluid-adapters/superfluid-adapter-basert/src/recipe.rs b/crates/superfluid-adapters/superfluid-adapter-basert/src/recipe.rs index ffdbb9c..239aa01 100644 --- a/crates/superfluid-adapters/superfluid-adapter-basert/src/recipe.rs +++ b/crates/superfluid-adapters/superfluid-adapter-basert/src/recipe.rs @@ -84,6 +84,19 @@ pub fn version_of(name: &str) -> Option { pub fn plan(req: &Request, host: &Host) -> Result, String> { let pins = pinned(); + // With no version named the newest release is wanted, which only the listing knows; offline the pins answer. + if req.version.is_none() && req.from.is_none() { + return match fetch_json(recipe()["releases"].as_str().unwrap_or_default()) { + Ok(releases) => plan_with(req, host, &pins, &releases), + Err(listing) => { + let mut plans = plan_with(req, host, &pins, &Value::Null).map_err(|why| format!("{why} ({listing})"))?; + for p in &mut plans { + p.why = format!("{} (the newest pinned: {listing})", p.why); + } + Ok(plans) + } + }; + } plan_with(req, host, &pins, &Value::Null).or_else(|why| { if req.from.is_some() { return Err(why); @@ -189,12 +202,14 @@ pub fn plan_with(req: &Request, host: &Host, pins: &[Pin], releases: &Value) -> .as_array() .map(|rs| { rs.iter() - .filter(|r| !r["draft"].as_bool().unwrap_or(false)) + .filter(|r| !r["draft"].as_bool().unwrap_or(false) && !r["prerelease"].as_bool().unwrap_or(false)) .filter_map(|r| { let version = r["tag_name"].as_str()?.trim_start_matches('v').to_string(); let want = engine(&version); let a = r["assets"].as_array()?.iter().find(|a| a["name"] == want.as_str())?; - let asset = Asset { name: want, url: a["browser_download_url"].as_str()?.to_string(), sha256: None, size: a["size"].as_u64() }; + // GitHub's own digest of the file as uploaded, the one the release's `.sha256` states too. + let sha256 = a["digest"].as_str().and_then(|d| d.strip_prefix("sha256:")).map(str::to_string); + let asset = Asset { name: want, url: a["browser_download_url"].as_str()?.to_string(), sha256, size: a["size"].as_u64() }; Some((version, abi_marked(r), asset)) }) .collect() @@ -207,11 +222,11 @@ pub fn plan_with(req: &Request, host: &Host, pins: &[Pin], releases: &Value) -> let sha256 = req.sha256.clone().or(pinned.sha256.clone()); return Ok((version, Asset { sha256, ..pinned })); } - match &req.sha256 { - Some(sha) => Ok((version, Asset { sha256: Some(sha.clone()), ..asset })), + match req.sha256.clone().or(asset.sha256.clone()) { + Some(sha) => Ok((version, Asset { sha256: Some(sha), ..asset })), None => Err(format!( - "basert {version} has no SHA-256 pinned in this superfluid, so it is not installed on what the release page alone \ - says: check the engine's SHA-256 (baseRT publishes it as {}.sha256) and pass it with --version {version} --sha256 ", + "basert {version} has no SHA-256 pinned in this superfluid, and GitHub lists none for its engine: check the engine's \ + SHA-256 (baseRT publishes it as {}.sha256) and pass it with --version {version} --sha256 ", asset.name )), } @@ -229,7 +244,10 @@ pub fn plan_with(req: &Request, host: &Host, pins: &[Pin], releases: &Value) -> (version, found.1, asset) } None => { - let found = pinned_here.into_iter().find(|(v, abi, _)| drives(v, *abi)).or_else(|| published.into_iter().find(|(v, abi, _)| drives(v, *abi))); + // The newest this build drives, pinned or not; a pin stays ahead of the listed copy of its own version. + let mut known: Vec<(String, Option, Asset)> = pinned_here.into_iter().chain(published).collect(); + known.sort_by_key(|(v, _, _)| std::cmp::Reverse(version_key(v))); + let found = known.into_iter().find(|(v, abi, _)| drives(v, *abi)); let found = found.ok_or_else(|| { format!( "baseRT publishes no engine this superfluid drives ({}) for {part} yet (the newest is {newest}; this superfluid was built \ @@ -334,18 +352,43 @@ mod tests { } #[test] - fn a_release_with_no_pinned_digest_needs_one_named() { + fn the_newest_release_this_superfluid_drives_is_installed_pinned_or_not() { + let (same, other) = (abi(), abi() + 1); + let pins = [pin("0.3.9", None, "cd"), pin("0.4.5", Some(same), "ef")]; + let mut releases = json!([release("0.3.9", None), release("0.4.5", Some(same)), release("0.4.6", Some(same)), release("0.5.0", Some(other))]); + let p = &plan_with(&Request::default(), &mac(), &pins, &releases).unwrap()[0]; + assert_eq!((p.version.as_str(), p.tested, p.assets[0].sha256.as_deref()), ("0.4.6", true, Some("ab")), "the digest GitHub lists"); + assert!(p.assets[0].url.starts_with("https://github.com/basecompute/baseRT/releases/download/v0.4.6/")); + assert_eq!(plan_with(&Request::default(), &mac(), &pins, &Value::Null).unwrap()[0].version, "0.4.5", "offline, the newest pin"); + releases.as_array_mut().unwrap().remove(2); + let p = &plan_with(&Request::default(), &mac(), &pins, &releases).unwrap()[0]; + assert_eq!((p.version.as_str(), p.assets[0].sha256.as_deref()), ("0.4.5", Some("ef")), "a listed release that is pinned keeps its pin"); + assert_eq!(p.assets[0].url, "https://pinned/basert-engine-macos-arm64-0.4.5.tar.gz"); + let mut pre = release("0.4.8", Some(same)); + pre["prerelease"] = json!(true); + let p = &plan_with(&Request::default(), &mac(), &pins, &json!([pre, release("0.4.7", Some(same))])).unwrap()[0]; + assert_eq!(p.version, "0.4.7", "a pre-release is never the newest"); + } + + #[test] + fn a_release_with_no_digest_pinned_or_listed_needs_one_named() { let same = "0.4.9".to_string(); - let releases = json!([release(&same, Some(abi()))]); + let p = &plan_with(&Request::default(), &mac(), &[], &json!([release(&same, Some(abi()))])).unwrap()[0]; + assert_eq!((p.version.as_str(), p.assets[0].sha256.as_deref()), (same.as_str(), Some("ab"))); + let mut unlisted = release(&same, Some(abi())); + unlisted["assets"][0].as_object_mut().unwrap().remove("digest"); + let releases = json!([unlisted]); let e = plan_with(&Request::default(), &mac(), &[], &releases).unwrap_err(); - assert!(e.contains(&format!("basert {same} has no SHA-256 pinned")) && e.contains("--sha256"), "{e}"); + assert!(e.contains(&format!("basert {same} has no SHA-256 pinned")) && e.contains("GitHub lists none") && e.contains("--sha256"), "{e}"); assert!(e.contains(&format!("basert-engine-macos-arm64-{same}.tar.gz.sha256")), "{e}"); let e = plan_with(&Request { version: Some(same.clone()), untested: true, ..Request::default() }, &mac(), &[], &releases).unwrap_err(); assert!(e.contains("has no SHA-256 pinned"), "--untested is about the version, not the digest: {e}"); let named = Request { version: Some(same.clone()), sha256: Some("12".into()), ..Request::default() }; - let p = &plan_with(&named, &mac(), &[], &releases).unwrap()[0]; - assert_eq!((p.assets[0].sha256.as_deref(), p.assets[0].size), (Some("12"), Some(10))); - assert!(p.assets[0].url.starts_with("https://github.com/basecompute/baseRT/releases/download/")); + for releases in [releases, json!([release(&same, Some(abi()))])] { + let p = &plan_with(&named, &mac(), &[], &releases).unwrap()[0]; + assert_eq!((p.assets[0].sha256.as_deref(), p.assets[0].size), (Some("12"), Some(10)), "the digest named wins"); + assert!(p.assets[0].url.starts_with("https://github.com/basecompute/baseRT/releases/download/")); + } } #[test] diff --git a/docs/models/runtimes.md b/docs/models/runtimes.md index 6e8432d..e381e94 100644 --- a/docs/models/runtimes.md +++ b/docs/models/runtimes.md @@ -71,7 +71,7 @@ SYCL and OpenVINO builds are installed only with `--backend`. **MLX.** Apple silicon only. Fetches CPython 3.12.14 (python-build-standalone) and, from PyPI, `mlx`, `mlx-metal`, `mlx-lm` and their dependencies, every wheel pinned by hash (tested: mlx 0.32.2, mlx-lm 0.31.3). Installs with `pip --require-hashes --no-deps --only-binary=:all:`; your own Python and `PYTHON*`/`PIP_*` settings are not used (pip's network settings excepted). -**basert.** Engine bundles from baseRT's releases for `macos-arm64` (Metal) and `linux-arm64-cuda`; other machines are refused. A release is tested when its major.minor matches the engine headers this daemon was built with; another needs `--untested` and may not load. Each engine's SHA-256 is pinned in the adapter; a release without a pin installs only with `--version V --sha256 H` (baseRT publishes each engine's digest beside it as `.sha256`), never on the release page's word alone. Instead of installing, you can name an existing library with `--basert-lib` or `BASERT_LIB`. +**basert.** Engine bundles from baseRT's releases for `macos-arm64` (Metal) and `linux-arm64-cuda`; other machines are refused. A release is tested when the engine ABI number it publishes beside its bundles (`basert-engine-abi-.txt`) is the one this daemon was built for, or, for a 0.3.x release, which states none, by its line; another needs `--untested` and may not load. With no `--version`, the newest tested release on baseRT's release page is installed, checked against the SHA-256 GitHub lists for its engine (the digest baseRT also publishes beside it as `.sha256`); the adapter also pins releases with their digests, which install the same way offline, when the release page cannot be read. A release GitHub lists no digest for, and that is not pinned, installs only with `--version V --sha256 H`. Instead of installing, you can name an existing library with `--basert-lib` or `BASERT_LIB`. ## How a worker is found diff --git a/docs/release-notes/0.1.4.md b/docs/release-notes/0.1.4.md index 942fe1f..e472e04 100644 --- a/docs/release-notes/0.1.4.md +++ b/docs/release-notes/0.1.4.md @@ -15,5 +15,12 @@ superfluid serve basecompute/Qwen3.8-27B-BaseQuant-V1 --pull-file Qwen3.8-27B-Ba `.base` extension is optional, and a name several directories share is given with its path. It needs basert 0.4.1 or later. +## Fixed + +- `superfluid runtime install basert`, and a `serve` that installs the baseRT + engine first, take the newest baseRT release this build serves. They took + 0.3.0, and a newer release only with `--sha256`, so `--pull-file` failed on + a fresh install. + See [supported models](https://github.com/basecompute/superfluid/blob/v0.1.4/docs/models/supported_models.md) for every runtime's pull options. From e22a3acbd8a6df982fed42e0ec8a18595c2e02c7 Mon Sep 17 00:00:00 2001 From: IsuraManchanayake Date: Fri, 23 Oct 2026 16:08:25 +1100 Subject: [PATCH 2/3] kit: a JSON lookup gives up by a deadline fetch_json ran curl with no deadline, so a listing that connects and then stalls blocked forever; with the basert listing now read ahead of the pins, that stopped a fresh install or serve from reaching the pinned fallback. Every lookup now ends within 20 s, retries included (--connect-timeout, --max-time, --retry-max-time), and fetch_json_within takes another deadline. Its test points a lookup at a socket that accepts and never answers: it blocked past 15 s before, and gives up in 2 s now. --- .../superfluid-adapter-kit/src/install.rs | 20 +++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/crates/superfluid-adapters/superfluid-adapter-kit/src/install.rs b/crates/superfluid-adapters/superfluid-adapter-kit/src/install.rs index e3c7dff..1554431 100644 --- a/crates/superfluid-adapters/superfluid-adapter-kit/src/install.rs +++ b/crates/superfluid-adapters/superfluid-adapter-kit/src/install.rs @@ -2,6 +2,7 @@ use std::path::{Path, PathBuf}; use std::process::Command; +use std::time::Duration; use serde_json::{json, Value}; @@ -402,11 +403,18 @@ pub fn fetch(asset: &Asset, dir: &Path) -> Result<(PathBuf, String), String> { } pub fn fetch_json(url: &str) -> Result { + fetch_json_within(url, Duration::from_secs(20)) +} + +/// A lookup a caller falls back from must end: an answer that stalls fails once `deadline` has passed, retries included. +pub fn fetch_json_within(url: &str, deadline: Duration) -> Result { if !url.starts_with("https://") { return Err(format!("{url}: only https:// URLs are fetched")); } + let secs = deadline.as_secs().max(1).to_string(); let mut curl = Command::new("curl"); curl.args(["-fsSL", "--proto", "=https", "--tlsv1.2", "--retry", "2", "-H", "Accept: application/json"]); + curl.args(["--connect-timeout", &secs, "--max-time", &secs, "--retry-max-time", &secs]); // GitHub's API allows sixty unauthenticated requests an hour per address, which hosted // runners share; a token in the environment lifts that, for the API alone. if url.starts_with("https://api.github.com/") { @@ -573,6 +581,18 @@ pub fn own_install() -> Option { mod tests { use super::*; + #[test] + fn a_json_lookup_that_stalls_gives_up_by_its_deadline() { + // A server that takes the connection and never answers, as a stalled API does. + let server = std::net::TcpListener::bind("127.0.0.1:0").unwrap(); + let url = format!("https://127.0.0.1:{}/releases", server.local_addr().unwrap().port()); + let (tx, rx) = std::sync::mpsc::channel(); + std::thread::spawn(move || tx.send(fetch_json_within(&url, Duration::from_secs(2)))); + let got = rx.recv_timeout(Duration::from_secs(15)).expect("the lookup gives up by its deadline"); + assert!(got.is_err(), "{got:?}"); + drop(server); + } + #[test] fn the_driver_version_is_read_from_the_kernel_module_line() { let line = "NVRM version: NVIDIA UNIX Open Kernel Module for aarch64 580.95.05 Release Build (dvs-builder@U16-I1-N08-12-3) Thu Sep 18 2025\nGCC version: gcc 13"; From 2dbb5a9d62a2fe7605b739dbf0149a2a2afc4f77 Mon Sep 17 00:00:00 2001 From: IsuraManchanayake Date: Fri, 23 Oct 2026 16:08:25 +1100 Subject: [PATCH 3/3] basert: a pre-release is installed when named The listing dropped pre-releases before both the default pick and a named --version, so runtime install basert --version of a GitHub pre-release, which installed before, said no such engine is published. Pre-releases are now left out of the newest only. --- .../superfluid-adapter-basert/src/recipe.rs | 33 +++++++++++++++++-- 1 file changed, 30 insertions(+), 3 deletions(-) diff --git a/crates/superfluid-adapters/superfluid-adapter-basert/src/recipe.rs b/crates/superfluid-adapters/superfluid-adapter-basert/src/recipe.rs index 239aa01..547fb33 100644 --- a/crates/superfluid-adapters/superfluid-adapter-basert/src/recipe.rs +++ b/crates/superfluid-adapters/superfluid-adapter-basert/src/recipe.rs @@ -198,11 +198,18 @@ pub fn plan_with(req: &Request, host: &Host, pins: &[Pin], releases: &Value) -> extra: Value::Null, }]); } + let prerelease: Vec = releases + .as_array() + .into_iter() + .flatten() + .filter(|r| r["prerelease"].as_bool().unwrap_or(false)) + .filter_map(|r| Some(r["tag_name"].as_str()?.trim_start_matches('v').to_string())) + .collect(); let mut published: Vec<(String, Option, Asset)> = releases .as_array() .map(|rs| { rs.iter() - .filter(|r| !r["draft"].as_bool().unwrap_or(false) && !r["prerelease"].as_bool().unwrap_or(false)) + .filter(|r| !r["draft"].as_bool().unwrap_or(false)) .filter_map(|r| { let version = r["tag_name"].as_str()?.trim_start_matches('v').to_string(); let want = engine(&version); @@ -244,8 +251,10 @@ pub fn plan_with(req: &Request, host: &Host, pins: &[Pin], releases: &Value) -> (version, found.1, asset) } None => { - // The newest this build drives, pinned or not; a pin stays ahead of the listed copy of its own version. - let mut known: Vec<(String, Option, Asset)> = pinned_here.into_iter().chain(published).collect(); + // The newest this build drives, pinned or not; a pin stays ahead of the listed copy of its own version, and a + // pre-release is installed only when named. + let listed = published.into_iter().filter(|(v, _, _)| !prerelease.contains(v)); + let mut known: Vec<(String, Option, Asset)> = pinned_here.into_iter().chain(listed).collect(); known.sort_by_key(|(v, _, _)| std::cmp::Reverse(version_key(v))); let found = known.into_iter().find(|(v, abi, _)| drives(v, *abi)); let found = found.ok_or_else(|| { @@ -370,6 +379,24 @@ mod tests { assert_eq!(p.version, "0.4.7", "a pre-release is never the newest"); } + #[test] + fn a_pre_release_is_installed_when_named_and_never_as_the_newest() { + let same = abi(); + let mut pre = release("0.4.8", Some(same)); + pre["prerelease"] = json!(true); + let releases = json!([pre, release("0.4.7", Some(same))]); + assert_eq!(plan_with(&Request::default(), &mac(), &[], &releases).unwrap()[0].version, "0.4.7"); + let named = Request { version: Some("0.4.8".into()), ..Request::default() }; + let p = &plan_with(&named, &mac(), &[], &releases).unwrap()[0]; + assert_eq!((p.version.as_str(), p.tested, p.assets[0].sha256.as_deref()), ("0.4.8", true, Some("ab"))); + // One of another ABI, as before: with --untested and a digest of one's own. + let mut other = release("0.5.1", Some(same + 1)); + other["prerelease"] = json!(true); + let req = Request { version: Some("0.5.1".into()), untested: true, sha256: Some("12".into()), ..Request::default() }; + let p = &plan_with(&req, &mac(), &[], &json!([other])).unwrap()[0]; + assert_eq!((p.version.as_str(), p.tested, p.assets[0].sha256.as_deref()), ("0.5.1", false, Some("12"))); + } + #[test] fn a_release_with_no_digest_pinned_or_listed_needs_one_named() { let same = "0.4.9".to_string();