rust: delete the duplicate SDK — ml/hanzo-research is the one

I built a Rust producer last night on the finding that none existed. That
finding was wrong: ml/hanzo-research already had one — 930 lines, tests, a
working kernel_perf example, from_env(), the same verbs against the same
/v1/research endpoint. I searched this repo, concluded absence, and did not
search ml.

Two SDKs for one plane is the defect the plane exists to prevent, so the
duplicate goes. ml/hanzo-research is canonical; it lives where the GPU
harnesses that must call it live.

Co-authored-by: Hanzo Dev <[email protected]>
This commit is contained in:
zooqueen
2026-07-28 00:12:04 -07:00
co-authored by Hanzo Dev
parent b5dd77d975
commit 24ace3b88b
5 changed files with 0 additions and 1765 deletions
-629
View File
@@ -1,629 +0,0 @@
# This file is automatically @generated by Cargo.
# It is not intended for manual editing.
version = 3
[[package]]
name = "base64"
version = "0.22.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6"
[[package]]
name = "cc"
version = "1.2.48"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c481bdbf0ed3b892f6f806287d72acd515b352a4ec27a208489b8c1bc839633a"
dependencies = [
"find-msvc-tools",
"shlex",
]
[[package]]
name = "cfg-if"
version = "1.0.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801"
[[package]]
name = "displaydoc"
version = "0.2.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "find-msvc-tools"
version = "0.1.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3a3076410a55c90011c298b04d0cfa770b00fa04e1e3c97d3f6c9de105a03844"
[[package]]
name = "form_urlencoded"
version = "1.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cb4cb245038516f5f85277875cdaa4f7d2c9a0fa0468de06ed190163b1581fcf"
dependencies = [
"percent-encoding",
]
[[package]]
name = "getrandom"
version = "0.2.16"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "335ff9f135e4384c8150d6f27c6daed433577f86b4750418338c01a1a2528592"
dependencies = [
"cfg-if",
"libc",
"wasi",
]
[[package]]
name = "hanzo-research"
version = "0.1.0"
dependencies = [
"serde",
"serde_json",
"ureq",
]
[[package]]
name = "icu_collections"
version = "2.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4c6b649701667bbe825c3b7e6388cb521c23d88644678e83c0c4d0a621a34b43"
dependencies = [
"displaydoc",
"potential_utf",
"yoke",
"zerofrom",
"zerovec",
]
[[package]]
name = "icu_locale_core"
version = "2.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "edba7861004dd3714265b4db54a3c390e880ab658fec5f7db895fae2046b5bb6"
dependencies = [
"displaydoc",
"litemap",
"tinystr",
"writeable",
"zerovec",
]
[[package]]
name = "icu_normalizer"
version = "2.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5f6c8828b67bf8908d82127b2054ea1b4427ff0230ee9141c54251934ab1b599"
dependencies = [
"icu_collections",
"icu_normalizer_data",
"icu_properties",
"icu_provider",
"smallvec",
"zerovec",
]
[[package]]
name = "icu_normalizer_data"
version = "2.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7aedcccd01fc5fe81e6b489c15b247b8b0690feb23304303a9e560f37efc560a"
[[package]]
name = "icu_properties"
version = "2.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e93fcd3157766c0c8da2f8cff6ce651a31f0810eaa1c51ec363ef790bbb5fb99"
dependencies = [
"icu_collections",
"icu_locale_core",
"icu_properties_data",
"icu_provider",
"zerotrie",
"zerovec",
]
[[package]]
name = "icu_properties_data"
version = "2.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "02845b3647bb045f1100ecd6480ff52f34c35f82d9880e029d329c21d1054899"
[[package]]
name = "icu_provider"
version = "2.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "85962cf0ce02e1e0a629cc34e7ca3e373ce20dda4c4d7294bbd0bf1fdb59e614"
dependencies = [
"displaydoc",
"icu_locale_core",
"writeable",
"yoke",
"zerofrom",
"zerotrie",
"zerovec",
]
[[package]]
name = "idna"
version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3b0875f23caa03898994f6ddc501886a45c7d3d62d04d2d90788d47be1b1e4de"
dependencies = [
"idna_adapter",
"smallvec",
"utf8_iter",
]
[[package]]
name = "idna_adapter"
version = "1.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3acae9609540aa318d1bc588455225fb2085b9ed0c4f6bd0d9d5bcd86f1a0344"
dependencies = [
"icu_normalizer",
"icu_properties",
]
[[package]]
name = "itoa"
version = "1.0.15"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4a5f13b858c8d314ee3e8f639011f7ccefe71f97f96e50151fb991f267928e2c"
[[package]]
name = "libc"
version = "0.2.178"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "37c93d8daa9d8a012fd8ab92f088405fb202ea0b6ab73ee2482ae66af4f42091"
[[package]]
name = "litemap"
version = "0.8.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6373607a59f0be73a39b6fe456b8192fcc3585f602af20751600e974dd455e77"
[[package]]
name = "log"
version = "0.4.29"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5e5032e24019045c762d3c0f28f5b6b8bbf38563a65908389bf7978758920897"
[[package]]
name = "memchr"
version = "2.7.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f52b00d39961fc5b2736ea853c9cc86238e165017a493d1d5c8eac6bdc4cc273"
[[package]]
name = "once_cell"
version = "1.21.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d"
[[package]]
name = "percent-encoding"
version = "2.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9b4f627cb1b25917193a259e49bdad08f671f8d9708acfd5fe0a8c1455d87220"
[[package]]
name = "potential_utf"
version = "0.1.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b73949432f5e2a09657003c25bca5e19a0e9c84f8058ca374f49e0ebe605af77"
dependencies = [
"zerovec",
]
[[package]]
name = "proc-macro2"
version = "1.0.103"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5ee95bc4ef87b8d5ba32e8b7714ccc834865276eab0aed5c9958d00ec45f49e8"
dependencies = [
"unicode-ident",
]
[[package]]
name = "quote"
version = "1.0.42"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a338cc41d27e6cc6dce6cefc13a0729dfbb81c262b1f519331575dd80ef3067f"
dependencies = [
"proc-macro2",
]
[[package]]
name = "ring"
version = "0.17.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a4689e6c2294d81e88dc6261c768b63bc4fcdb852be6d1352498b114f61383b7"
dependencies = [
"cc",
"cfg-if",
"getrandom",
"libc",
"untrusted",
"windows-sys",
]
[[package]]
name = "rustls"
version = "0.23.36"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c665f33d38cea657d9614f766881e4d510e0eda4239891eea56b4cadcf01801b"
dependencies = [
"log",
"once_cell",
"ring",
"rustls-pki-types",
"rustls-webpki",
"subtle",
"zeroize",
]
[[package]]
name = "rustls-pki-types"
version = "1.13.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "708c0f9d5f54ba0272468c1d306a52c495b31fa155e91bc25371e6df7996908c"
dependencies = [
"zeroize",
]
[[package]]
name = "rustls-webpki"
version = "0.103.13"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "61c429a8649f110dddef65e2a5ad240f747e85f7758a6bccc7e5777bd33f756e"
dependencies = [
"ring",
"rustls-pki-types",
"untrusted",
]
[[package]]
name = "ryu"
version = "1.0.20"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "28d3b2b1366ec20994f1fd18c3c594f05c5dd4bc44d8bb0c1c632c8d6829481f"
[[package]]
name = "serde"
version = "1.0.228"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9a8e94ea7f378bd32cbbd37198a4a91436180c5bb472411e48b5ec2e2124ae9e"
dependencies = [
"serde_core",
"serde_derive",
]
[[package]]
name = "serde_core"
version = "1.0.228"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "41d385c7d4ca58e59fc732af25c3983b67ac852c1a25000afe1175de458b67ad"
dependencies = [
"serde_derive",
]
[[package]]
name = "serde_derive"
version = "1.0.228"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "serde_json"
version = "1.0.145"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "402a6f66d8c709116cf22f558eab210f5a50187f702eb4d7e5ef38d9a7f1c79c"
dependencies = [
"itoa",
"memchr",
"ryu",
"serde",
"serde_core",
]
[[package]]
name = "shlex"
version = "1.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64"
[[package]]
name = "smallvec"
version = "1.15.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03"
[[package]]
name = "stable_deref_trait"
version = "1.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6ce2be8dc25455e1f91df71bfa12ad37d7af1092ae736f3a6cd0e37bc7810596"
[[package]]
name = "subtle"
version = "2.6.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292"
[[package]]
name = "syn"
version = "2.0.111"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "390cc9a294ab71bdb1aa2e99d13be9c753cd2d7bd6560c77118597410c4d2e87"
dependencies = [
"proc-macro2",
"quote",
"unicode-ident",
]
[[package]]
name = "synstructure"
version = "0.13.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "728a70f3dbaf5bab7f0c4b1ac8d7ae5ea60a4b5549c8a5914361c99147a709d2"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "tinystr"
version = "0.8.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "42d3e9c45c09de15d06dd8acf5f4e0e399e85927b7f00711024eb7ae10fa4869"
dependencies = [
"displaydoc",
"zerovec",
]
[[package]]
name = "unicode-ident"
version = "1.0.22"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9312f7c4f6ff9069b165498234ce8be658059c6728633667c526e27dc2cf1df5"
[[package]]
name = "untrusted"
version = "0.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1"
[[package]]
name = "ureq"
version = "2.12.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "02d1a66277ed75f640d608235660df48c8e3c19f3b4edb6a263315626cc3c01d"
dependencies = [
"base64",
"log",
"once_cell",
"rustls",
"rustls-pki-types",
"serde",
"serde_json",
"url",
"webpki-roots 0.26.11",
]
[[package]]
name = "url"
version = "2.5.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "08bc136a29a3d1758e07a9cca267be308aeebf5cfd5a10f3f67ab2097683ef5b"
dependencies = [
"form_urlencoded",
"idna",
"percent-encoding",
"serde",
]
[[package]]
name = "utf8_iter"
version = "1.0.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be"
[[package]]
name = "wasi"
version = "0.11.1+wasi-snapshot-preview1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b"
[[package]]
name = "webpki-roots"
version = "0.26.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "521bc38abb08001b01866da9f51eb7c5d647a19260e00054a8c7fd5f9e57f7a9"
dependencies = [
"webpki-roots 1.0.4",
]
[[package]]
name = "webpki-roots"
version = "1.0.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b2878ef029c47c6e8cf779119f20fcf52bde7ad42a731b2a304bc221df17571e"
dependencies = [
"rustls-pki-types",
]
[[package]]
name = "windows-sys"
version = "0.52.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d"
dependencies = [
"windows-targets",
]
[[package]]
name = "windows-targets"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973"
dependencies = [
"windows_aarch64_gnullvm",
"windows_aarch64_msvc",
"windows_i686_gnu",
"windows_i686_gnullvm",
"windows_i686_msvc",
"windows_x86_64_gnu",
"windows_x86_64_gnullvm",
"windows_x86_64_msvc",
]
[[package]]
name = "windows_aarch64_gnullvm"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3"
[[package]]
name = "windows_aarch64_msvc"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469"
[[package]]
name = "windows_i686_gnu"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b"
[[package]]
name = "windows_i686_gnullvm"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66"
[[package]]
name = "windows_i686_msvc"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66"
[[package]]
name = "windows_x86_64_gnu"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78"
[[package]]
name = "windows_x86_64_gnullvm"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d"
[[package]]
name = "windows_x86_64_msvc"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec"
[[package]]
name = "writeable"
version = "0.6.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9edde0db4769d2dc68579893f2306b26c6ecfbe0ef499b013d731b7b9247e0b9"
[[package]]
name = "yoke"
version = "0.8.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "72d6e5c6afb84d73944e5cedb052c4680d5657337201555f9f2a16b7406d4954"
dependencies = [
"stable_deref_trait",
"yoke-derive",
"zerofrom",
]
[[package]]
name = "yoke-derive"
version = "0.8.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b659052874eb698efe5b9e8cf382204678a0086ebf46982b79d6ca3182927e5d"
dependencies = [
"proc-macro2",
"quote",
"syn",
"synstructure",
]
[[package]]
name = "zerofrom"
version = "0.1.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "50cc42e0333e05660c3587f3bf9d0478688e15d870fab3346451ce7f8c9fbea5"
dependencies = [
"zerofrom-derive",
]
[[package]]
name = "zerofrom-derive"
version = "0.1.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d71e5d6e06ab090c67b5e44993ec16b72dcbaabc526db883a360057678b48502"
dependencies = [
"proc-macro2",
"quote",
"syn",
"synstructure",
]
[[package]]
name = "zeroize"
version = "1.8.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b97154e67e32c85465826e8bcc1c59429aaaf107c1e4a9e53c8d8ccd5eff88d0"
[[package]]
name = "zerotrie"
version = "0.2.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2a59c17a5562d507e4b54960e8569ebee33bee890c70aa3fe7b97e85a9fd7851"
dependencies = [
"displaydoc",
"yoke",
"zerofrom",
]
[[package]]
name = "zerovec"
version = "0.11.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6c28719294829477f525be0186d13efa9a3c602f7ec202ca9e353d310fb9a002"
dependencies = [
"yoke",
"zerofrom",
"zerovec-derive",
]
[[package]]
name = "zerovec-derive"
version = "0.11.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "eadce39539ca5cb3985590102671f2567e659fca9666581ad3411d59207951f3"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
-21
View File
@@ -1,21 +0,0 @@
[package]
name = "hanzo-research"
version = "0.1.0"
edition = "2021"
rust-version = "1.75"
license = "MIT"
description = "Rust SDK for the Hanzo /v1/research evidence plane — record a falsifiable experiment and its verdict straight to cloud."
repository = "https://github.com/hanzoai/research"
keywords = ["benchmark", "experiment", "evidence", "hanzo"]
categories = ["development-tools", "science"]
[dependencies]
# ureq is blocking on purpose: a benchmark harness is a blocking loop, and a
# production instrumentation path must not require a runtime to be present.
# 2.12 is already in the engine and ml lockfiles, so this drags no new graph.
ureq = { version = "2.12", default-features = false, features = ["tls", "json"] }
serde = { version = "1", features = ["derive"] }
serde_json = "1"
[dev-dependencies]
serde_json = "1"
-195
View File
@@ -1,195 +0,0 @@
// Copyright © 2026 Hanzo AI. MIT License.
//! The falsifiable frame: a hypothesis, a prediction, the attempts, and the verdict.
use crate::provenance::Host;
use serde::{Deserialize, Serialize};
/// The epistemic outcome of a falsifiable experiment — distinct from execution status.
///
/// A refutation is a first-class, durable result, recorded as clearly as a proof; that is
/// the whole point of an evidence plane. Being an enum rather than a string means an
/// out-of-set verdict cannot be constructed at all, which is stricter than the Go SDK's
/// validate-at-`Conclude` and costs nothing.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "lowercase")]
pub enum Verdict {
Proven,
Refuted,
Inconclusive,
}
impl Verdict {
pub fn as_str(self) -> &'static str {
match self {
Verdict::Proven => "proven",
Verdict::Refuted => "refuted",
Verdict::Inconclusive => "inconclusive",
}
}
}
/// One measured attempt's outcome.
///
/// Named `Attempt` rather than the Go SDK's `Result` to avoid shadowing `std::result::Result`
/// at every call site — the wire contract is unchanged, only the Rust-side name.
///
/// `status` of `faulted` or `failed` retains a negative result without counting it toward
/// the running accuracy: a crashed arm is evidence, but it is not a wrong answer.
#[derive(Debug, Clone, Default)]
pub struct Attempt {
pub answer: String,
pub correct: bool,
pub response: String,
pub gold: String,
/// Defaults to `hanzo-measured` when blank.
pub source: String,
/// Defaults to `complete` when blank.
pub status: String,
}
impl Attempt {
/// A correct attempt with just the answer recorded.
pub fn ok(answer: impl Into<String>) -> Self {
Attempt { answer: answer.into(), correct: true, ..Default::default() }
}
/// An incorrect attempt, carrying what was produced and what was expected.
pub fn wrong(answer: impl Into<String>, gold: impl Into<String>) -> Self {
Attempt { answer: answer.into(), gold: gold.into(), correct: false, ..Default::default() }
}
/// An arm that did not complete. Retained as evidence, excluded from accuracy.
pub fn faulted(response: impl Into<String>) -> Self {
Attempt {
response: response.into(),
correct: false,
status: "faulted".into(),
..Default::default()
}
}
pub(crate) fn normalized(mut self) -> Self {
if self.source.is_empty() {
self.source = "hanzo-measured".into();
}
if self.status.is_empty() {
self.status = "complete".into();
}
self
}
/// Whether this attempt counts toward the running accuracy denominator.
pub(crate) fn counts(&self) -> bool {
!matches!(self.status.as_str(), "faulted" | "failed")
}
}
/// The scientific frame plus the self-documenting narrative.
///
/// These json names are the cross-language contract, shared verbatim with the Go SDK and
/// the Python producer. The server keys and dedupes semantically, so a Rust harness and a
/// Python one land in the same row.
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
pub(crate) struct Meta {
pub doc: String,
pub commits: Vec<String>,
pub note: String,
pub host: Host,
pub hypothesis: String,
pub predict: String,
pub verdict: String,
pub because: String,
pub log: Vec<String>,
}
/// One experiment version on the wire. `project`, `ts` and `cost` are server-stamped.
#[derive(Debug, Clone, Serialize)]
pub(crate) struct ExpPayload {
pub id: String,
pub kind: String,
pub subject: String,
pub task: String,
pub metric: String,
pub value: f64,
pub n: i64,
pub n_total: i64,
pub status: String,
pub git_sha: String,
pub git_branch: String,
pub git_dirty: bool,
pub lib_versions: std::collections::BTreeMap<String, String>,
pub meta: Meta,
}
/// One measured attempt on the wire.
#[derive(Debug, Clone, Serialize)]
pub(crate) struct AttemptPayload {
pub benchmark: String,
pub item: String,
pub model: String,
pub answer: String,
pub correct: bool,
pub response: String,
pub gold: String,
pub source: String,
pub status: String,
}
/// One diary artifact. Bytes are base64 in `content`; the server content-addresses by sha256.
#[derive(Debug, Clone, Serialize)]
pub(crate) struct ArtifactPayload {
pub content: String,
pub sha256: String,
pub kind: String,
pub run_id: String,
pub git_sha: String,
pub git_branch: String,
pub git_dirty: bool,
pub lib_versions: std::collections::BTreeMap<String, String>,
}
/// One upload batch: experiments and their attempts as two flat arrays.
#[derive(Debug, Clone, Serialize)]
pub(crate) struct IngestReq {
pub experiments: Vec<ExpPayload>,
pub attempts: Vec<AttemptPayload>,
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn verdict_serializes_to_the_cross_language_strings() {
assert_eq!(serde_json::to_string(&Verdict::Proven).unwrap(), "\"proven\"");
assert_eq!(serde_json::to_string(&Verdict::Refuted).unwrap(), "\"refuted\"");
assert_eq!(
serde_json::to_string(&Verdict::Inconclusive).unwrap(),
"\"inconclusive\""
);
}
#[test]
fn a_faulted_arm_is_evidence_but_not_a_wrong_answer() {
assert!(!Attempt::faulted("oom").counts());
assert!(Attempt::wrong("4", "5").counts());
assert!(Attempt::ok("5").counts());
}
#[test]
fn blank_source_and_status_take_the_documented_defaults() {
let a = Attempt::ok("x").normalized();
assert_eq!(a.source, "hanzo-measured");
assert_eq!(a.status, "complete");
}
#[test]
fn ingest_body_always_carries_both_arrays() {
// The Go SDK converts nil to []; an absent key is a different wire shape and the
// server distinguishes them, so an empty batch must still emit both arrays.
let body = IngestReq { experiments: vec![], attempts: vec![] };
let j = serde_json::to_string(&body).unwrap();
assert_eq!(j, r#"{"experiments":[],"attempts":[]}"#);
}
}
-784
View File
@@ -1,784 +0,0 @@
// Copyright © 2026 Hanzo AI. MIT License.
//! Rust SDK for the Hanzo `/v1/research` evidence plane.
//!
//! The ONE way a Rust program records R&D evidence. Mirrors the Go SDK
//! (`github.com/hanzoai/research`) and the Python producer verb-for-verb: the server keys
//! each experiment on `(project, id = kind:subject:task)`, so a Rust harness, a Python
//! notebook and a Go service upsert the same row.
//!
//! Rust is where the GPU harnesses live, and until this crate existed they had nowhere to
//! record — which is why kernel results survived as loose `.log` files on the box that ran
//! them instead of as evidence anyone could query.
//!
//! # Inert until configured
//!
//! With no API key the client is **inert**: every verb is a cheap no-op that returns `Ok`.
//! That is what makes it safe to leave a `record` call on a production path — unconfigured
//! it costs a branch, and configured it records. Recording is best-effort by design: a
//! transport error is returned, never panicked, and never interrupts the work being measured.
//!
//! # `kind` is open, on purpose
//!
//! `kind` is a free string, never an enum: `benchmark`, `kernel-perf`, `training`,
//! `ablation` **and** `marketing-experiment`, `ad-test`, `pricing-test`, `growth-experiment`.
//! A marketing A/B records identically to a kernel A/B — state a hypothesis, record the
//! arms, conclude proven or refuted. That symmetry is the point: one plane measures whether
//! a change helped, whatever the change was.
//!
//! ```no_run
//! use hanzo_research::{Research, Config, Verdict, Attempt};
//!
//! let c = Research::new(Config { project: "engine-bench".into(), ..Default::default() });
//! let mut exp = c
//! .experiment("kernel-perf", "gfx1151", "q4k-decode")
//! .metric("ratio")
//! .hypothesis("the DSL i8 coopmat arm beats the dp4a hand tile")
//! .predict("ratio > 1.0 across ffn/attn shapes")
//! .start()?;
//!
//! exp.record("n=4096", "mmq_q4k_rt", Attempt::wrong("0.62", "1.0"))?;
//! exp.log("i8 matrix-core does not reach f16 matrix-core throughput here");
//! exp.conclude(Verdict::Refuted, "0.51-0.78x of the hand tile; int8 alone will not close it", 0.62)?;
//! # Ok::<(), hanzo_research::Error>(())
//! ```
mod experiment;
mod provenance;
pub use experiment::{Attempt, Verdict};
pub use provenance::{find_repo, lib_version, Host, Vcs};
use experiment::{ArtifactPayload, AttemptPayload, ExpPayload, IngestReq, Meta};
use serde::{Deserialize, Serialize};
use std::collections::BTreeMap;
use std::time::Duration;
const DEFAULT_BASE: &str = "https://api.hanzo.ai";
const DEFAULT_TIMEOUT: Duration = Duration::from_secs(30);
/// Bound the response read so a hostile or broken server cannot stream an unbounded body.
const MAX_RESPONSE: usize = 8 << 20;
/// What can go wrong recording evidence. Every variant is returned, never panicked.
#[derive(Debug)]
pub enum Error {
/// Transport failed — the experiment still happened, only the recording did not.
Transport(String),
/// The server rejected the request.
Server { method: &'static str, path: String, status: u16, message: String },
/// The response exceeded the read cap.
ResponseTooLarge { path: String, cap: usize },
/// The response was not the JSON we expect.
Decode(String),
}
impl std::fmt::Display for Error {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Error::Transport(e) => write!(f, "research: transport: {e}"),
Error::Server { method, path, status, message } => {
write!(f, "research: {method} {path}: {status}: {message}")
}
Error::ResponseTooLarge { path, cap } => {
write!(f, "research: {path}: response exceeds {cap}-byte cap")
}
Error::Decode(e) => write!(f, "research: decode: {e}"),
}
}
}
impl std::error::Error for Error {}
/// Client configuration. Every unset field falls back to the environment or a default.
#[derive(Debug, Clone, Default)]
pub struct Config {
/// Defaults to `$HANZO_API_BASE`, else `https://api.hanzo.ai`.
pub base: String,
/// Defaults to `$HANZO_API_KEY`. Empty leaves the client inert.
pub key: String,
/// Project sub-scope. Defaults to `$HANZO_PROJECT`.
pub project: String,
/// Repo to read provenance from. Defaults to the nearest enclosing git repo.
pub repo: String,
/// Library names whose `<NAME>_VERSION` env var is captured with each record.
pub libs: Vec<String>,
pub timeout: Option<Duration>,
}
/// A configured client: base URL, per-org key, project sub-scope, provenance repo.
#[derive(Debug, Clone)]
pub struct Research {
base: String,
key: String,
project: String,
repo: String,
libs: Vec<String>,
timeout: Duration,
}
fn env_or(explicit: &str, var: &str, fallback: &str) -> String {
if !explicit.is_empty() {
return explicit.to_string();
}
std::env::var(var)
.ok()
.filter(|v| !v.trim().is_empty())
.unwrap_or_else(|| fallback.to_string())
}
impl Research {
/// Builds a client, filling every unset field from the environment or its default.
///
/// The key is read from `$HANZO_API_KEY` — sourced from KMS through the process
/// environment, never hardcoded.
pub fn new(cfg: Config) -> Self {
let repo = if cfg.repo.is_empty() { find_repo("") } else { cfg.repo.clone() };
Research {
base: env_or(&cfg.base, "HANZO_API_BASE", DEFAULT_BASE)
.trim_end_matches('/')
.to_string(),
key: env_or(&cfg.key, "HANZO_API_KEY", ""),
project: env_or(&cfg.project, "HANZO_PROJECT", ""),
repo,
libs: cfg.libs,
timeout: cfg.timeout.unwrap_or(DEFAULT_TIMEOUT),
}
}
/// Whether this client will actually transmit.
///
/// Unconfigured clients are inert rather than failing, so a `record` call can be left
/// in a production path and simply cost nothing where no key is provisioned.
pub fn enabled(&self) -> bool {
!self.key.is_empty()
}
/// Opens an experiment handle for `kind:subject:task`.
///
/// `kind` is an open string — `kernel-perf` and `marketing-experiment` are equally valid.
pub fn experiment<'a>(
&'a self,
kind: &str,
subject: &str,
task: &str,
) -> ExperimentBuilder<'a> {
ExperimentBuilder {
client: self,
kind: kind.into(),
subject: subject.into(),
task: task.into(),
metric: String::new(),
n_total: 0,
note: String::new(),
hypothesis: String::new(),
predict: String::new(),
}
}
/// Lists canonical experiments, optionally narrowed by project and kind.
pub fn query(&self, project: &str, kind: &str) -> Result<Vec<Run>, Error> {
if !self.enabled() {
return Ok(Vec::new());
}
let mut path = String::from("/v1/research/experiments");
let mut q = Vec::new();
if !project.is_empty() {
q.push(format!("project={}", urlencode(project)));
}
if !kind.is_empty() {
q.push(format!("kind={}", urlencode(kind)));
}
if !q.is_empty() {
path.push('?');
path.push_str(&q.join("&"));
}
self.get(&path)
}
/// The headline aggregate plus per-kind totals.
pub fn totals(&self, project: &str) -> Result<Totals, Error> {
if !self.enabled() {
return Ok(Totals::default());
}
let mut path = String::from("/v1/research/totals");
if !project.is_empty() {
path.push_str(&format!("?project={}", urlencode(project)));
}
self.get(&path)
}
/// Sets visibility/consent for a stable id. Records are private by default; publication
/// is a separate authorized grant, never implied by recording.
pub fn grant(&self, g: GrantRequest) -> Result<i64, Error> {
if !self.enabled() {
return Ok(0);
}
let out: GrantResult = self.post("/v1/research/grants", &g)?;
Ok(out.updated)
}
fn ingest(&self, exps: Vec<ExpPayload>, atts: Vec<AttemptPayload>) -> Result<(), Error> {
if !self.enabled() {
return Ok(());
}
let _: serde_json::Value =
self.post("/v1/research/experiments", &IngestReq { experiments: exps, attempts: atts })?;
Ok(())
}
fn artifact(&self, a: ArtifactPayload) -> Result<(), Error> {
if !self.enabled() {
return Ok(());
}
let _: serde_json::Value = self.post("/v1/research/artifacts", &a)?;
Ok(())
}
/// Auth is ONLY the per-org key; the gateway mints the validated principal and project
/// scope from it. The client never sends `X-User-Id` / `X-Org-Id` — a cross-tenant forge
/// the gateway strips anyway.
fn request(&self, req: ureq::Request) -> ureq::Request {
let r = req
.set("Content-Type", "application/json")
.set("X-Project-Id", &self.project);
if self.key.is_empty() {
r
} else {
r.set("Authorization", &format!("Bearer {}", self.key))
}
}
fn agent(&self) -> ureq::Agent {
ureq::AgentBuilder::new().timeout(self.timeout).build()
}
fn post<B: Serialize, T: for<'de> Deserialize<'de>>(
&self,
path: &str,
body: &B,
) -> Result<T, Error> {
let url = format!("{}{}", self.base, path);
let resp = self
.request(self.agent().post(&url))
.send_json(serde_json::to_value(body).map_err(|e| Error::Decode(e.to_string()))?);
self.finish("POST", path, resp)
}
fn get<T: for<'de> Deserialize<'de>>(&self, path: &str) -> Result<T, Error> {
let url = format!("{}{}", self.base, path);
let resp = self.request(self.agent().get(&url)).call();
self.finish("GET", path, resp)
}
fn finish<T: for<'de> Deserialize<'de>>(
&self,
method: &'static str,
path: &str,
resp: Result<ureq::Response, ureq::Error>,
) -> Result<T, Error> {
let response = match resp {
Ok(r) => r,
Err(ureq::Error::Status(status, r)) => {
let msg = r.into_string().unwrap_or_default();
return Err(Error::Server {
method,
path: path.to_string(),
status,
message: server_error(&msg),
});
}
Err(e) => return Err(Error::Transport(e.to_string())),
};
let mut buf = String::new();
use std::io::Read;
response
.into_reader()
.take((MAX_RESPONSE + 1) as u64)
.read_to_string(&mut buf)
.map_err(|e| Error::Transport(e.to_string()))?;
if buf.len() > MAX_RESPONSE {
return Err(Error::ResponseTooLarge { path: path.to_string(), cap: MAX_RESPONSE });
}
if buf.trim().is_empty() {
buf.push_str("null");
}
serde_json::from_str(&buf).map_err(|e| Error::Decode(e.to_string()))
}
}
/// Pulls the server's message out of an error body, falling back to the raw text.
fn server_error(body: &str) -> String {
if let Ok(v) = serde_json::from_str::<serde_json::Value>(body) {
for k in ["error", "message", "msg"] {
if let Some(s) = v.get(k).and_then(|x| x.as_str()) {
return s.to_string();
}
}
}
body.chars().take(400).collect()
}
fn urlencode(s: &str) -> String {
s.bytes()
.map(|b| match b {
b'A'..=b'Z' | b'a'..=b'z' | b'0'..=b'9' | b'-' | b'_' | b'.' | b'~' => {
(b as char).to_string()
}
_ => format!("%{b:02X}"),
})
.collect()
}
/// Builds an experiment before it is opened.
pub struct ExperimentBuilder<'a> {
client: &'a Research,
kind: String,
subject: String,
task: String,
metric: String,
n_total: i64,
note: String,
hypothesis: String,
predict: String,
}
impl<'a> ExperimentBuilder<'a> {
/// Names the headline number's unit (`accuracy` | `tok/s` | `ratio` | `net-lift` | …).
pub fn metric(mut self, s: impl Into<String>) -> Self {
self.metric = s.into();
self
}
/// The denominator this run is measured against.
pub fn total(mut self, n: i64) -> Self {
self.n_total = n;
self
}
/// A one-shot note carried with the record.
pub fn note(mut self, s: impl Into<String>) -> Self {
self.note = s.into();
self
}
/// The falsifiable claim under test.
pub fn hypothesis(mut self, s: impl Into<String>) -> Self {
self.hypothesis = s.into();
self
}
/// What the hypothesis predicts will be observed.
pub fn predict(mut self, s: impl Into<String>) -> Self {
self.predict = s.into();
self
}
/// Opens the experiment and posts it in-flight as `running`.
pub fn start(self) -> Result<Experiment<'a>, Error> {
let vcs = Vcs::detect(&self.client.repo);
let libs: BTreeMap<String, String> = self
.client
.libs
.iter()
.filter_map(|l| lib_version(l).map(|v| (l.clone(), v)))
.collect();
let mut e = Experiment {
client: self.client,
id: format!("{}:{}:{}", self.kind, self.subject, self.task),
kind: self.kind,
subject: self.subject,
task: self.task,
metric: self.metric,
n_total: self.n_total,
n_ok: 0,
n_done: 0,
note: self.note,
hypothesis: self.hypothesis,
predict: self.predict,
because: String::new(),
log: Vec::new(),
vcs,
libs,
host: Host::detect(),
attempts: Vec::new(),
sealed: false,
};
e.post("running", 0.0)?;
Ok(e)
}
}
/// A handle to one run. Not `Sync`: an experiment is a single narrative.
pub struct Experiment<'a> {
client: &'a Research,
id: String,
kind: String,
subject: String,
task: String,
metric: String,
n_total: i64,
n_ok: i64,
n_done: i64,
note: String,
hypothesis: String,
predict: String,
because: String,
log: Vec<String>,
vcs: Vcs,
libs: BTreeMap<String, String>,
host: Host,
attempts: Vec<AttemptPayload>,
sealed: bool,
}
impl<'a> Experiment<'a> {
/// The stable id this run upserts: `kind:subject:task`.
pub fn id(&self) -> &str {
&self.id
}
/// Files one measured attempt. Idempotent server-side on `(benchmark, item, model)`.
pub fn record(
&mut self,
item: impl Into<String>,
model: impl Into<String>,
attempt: Attempt,
) -> Result<(), Error> {
let a = attempt.normalized();
if a.counts() {
self.n_done += 1;
if a.correct {
self.n_ok += 1;
}
}
self.attempts.push(AttemptPayload {
benchmark: self.id.clone(),
item: item.into(),
model: model.into(),
answer: a.answer,
correct: a.correct,
response: a.response,
gold: a.gold,
source: a.source,
status: a.status,
});
Ok(())
}
/// Appends to the running narrative.
pub fn log(&mut self, line: impl Into<String>) -> &mut Self {
self.log.push(line.into());
self
}
/// The accuracy implied by the attempts recorded so far.
pub fn accuracy(&self) -> f64 {
if self.n_done == 0 {
0.0
} else {
self.n_ok as f64 / self.n_done as f64
}
}
/// Seals the run with a verdict, the reasoning, and the headline number.
pub fn conclude(
&mut self,
verdict: Verdict,
because: impl Into<String>,
value: f64,
) -> Result<(), Error> {
self.because = because.into();
self.post_verdict(Some(verdict), value)
}
/// Seals the run without an explicit verdict.
///
/// A run that stated a hypothesis but never concluded is recorded `Inconclusive` rather
/// than silently complete — an untested claim is not a proven one.
pub fn finish(&mut self, value: f64) -> Result<(), Error> {
let v = if self.hypothesis.is_empty() { None } else { Some(Verdict::Inconclusive) };
self.post_verdict(v, value)
}
/// Files a diary artifact (a plot, a report). The server content-addresses it by sha256.
pub fn artifact(&self, kind: &str, bytes: &[u8]) -> Result<(), Error> {
self.client.artifact(ArtifactPayload {
content: b64(bytes),
sha256: sha256_hex(bytes),
kind: kind.to_string(),
run_id: self.id.clone(),
git_sha: self.vcs.sha.clone(),
git_branch: self.vcs.branch.clone(),
git_dirty: self.vcs.dirty,
lib_versions: self.libs.clone(),
})
}
fn post_verdict(&mut self, verdict: Option<Verdict>, value: f64) -> Result<(), Error> {
if let Some(v) = verdict {
self.log.push(format!("verdict: {}", v.as_str()));
}
let v = verdict.map(|v| v.as_str().to_string()).unwrap_or_default();
self.sealed = true;
self.post_with("complete", value, &v)
}
fn post(&mut self, status: &str, value: f64) -> Result<(), Error> {
self.post_with(status, value, "")
}
fn post_with(&mut self, status: &str, value: f64, verdict: &str) -> Result<(), Error> {
let exp = ExpPayload {
id: self.id.clone(),
kind: self.kind.clone(),
subject: self.subject.clone(),
task: self.task.clone(),
metric: self.metric.clone(),
value,
n: self.n_done,
n_total: if self.n_total > 0 { self.n_total } else { self.n_done },
status: status.to_string(),
git_sha: self.vcs.sha.clone(),
git_branch: self.vcs.branch.clone(),
git_dirty: self.vcs.dirty,
lib_versions: self.libs.clone(),
meta: Meta {
doc: String::new(),
commits: Vec::new(),
note: std::mem::take(&mut self.note),
host: self.host.clone(),
hypothesis: self.hypothesis.clone(),
predict: self.predict.clone(),
verdict: verdict.to_string(),
because: self.because.clone(),
log: self.log.clone(),
},
};
let atts = std::mem::take(&mut self.attempts);
self.client.ingest(vec![exp], atts)
}
}
/// One stored experiment version returned by [`Research::query`].
#[derive(Debug, Clone, Default, Deserialize)]
pub struct Run {
#[serde(default)]
pub project: String,
#[serde(default)]
pub id: String,
#[serde(default)]
pub kind: String,
#[serde(default)]
pub subject: String,
#[serde(default)]
pub task: String,
#[serde(default)]
pub metric: String,
#[serde(default)]
pub value: f64,
#[serde(default)]
pub n: i64,
#[serde(default)]
pub status: String,
#[serde(default)]
pub git_sha: String,
#[serde(default)]
pub meta: serde_json::Value,
}
/// Headline aggregate plus per-kind totals.
#[derive(Debug, Clone, Default, Deserialize)]
pub struct Totals {
#[serde(default)]
pub experiments: i64,
#[serde(default)]
pub attempts: i64,
#[serde(default)]
pub cost_usd: f64,
#[serde(default)]
pub kinds: Vec<KindTotal>,
}
#[derive(Debug, Clone, Default, Deserialize)]
pub struct KindTotal {
#[serde(default)]
pub kind: String,
#[serde(default)]
pub experiments: i64,
#[serde(default)]
pub attempts: i64,
}
/// Visibility/consent for a stable id.
#[derive(Debug, Clone, Default, Serialize)]
pub struct GrantRequest {
pub id: String,
#[serde(skip_serializing_if = "String::is_empty")]
pub project: String,
#[serde(skip_serializing_if = "String::is_empty")]
pub visibility: String,
pub trainable: bool,
pub publishable: bool,
}
#[derive(Debug, Deserialize)]
struct GrantResult {
#[serde(default)]
updated: i64,
}
// --- small self-contained encodings, so the crate stays dependency-light ---
const B64: &[u8; 64] = b"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
fn b64(data: &[u8]) -> String {
let mut out = String::with_capacity(data.len().div_ceil(3) * 4);
for c in data.chunks(3) {
let b = [c[0], *c.get(1).unwrap_or(&0), *c.get(2).unwrap_or(&0)];
let n = ((b[0] as u32) << 16) | ((b[1] as u32) << 8) | b[2] as u32;
out.push(B64[(n >> 18 & 63) as usize] as char);
out.push(B64[(n >> 12 & 63) as usize] as char);
out.push(if c.len() > 1 { B64[(n >> 6 & 63) as usize] as char } else { '=' });
out.push(if c.len() > 2 { B64[(n & 63) as usize] as char } else { '=' });
}
out
}
fn sha256_hex(data: &[u8]) -> String {
const K: [u32; 64] = [
0x428a2f98, 0x71374491, 0xb5c0fbcf, 0xe9b5dba5, 0x3956c25b, 0x59f111f1, 0x923f82a4,
0xab1c5ed5, 0xd807aa98, 0x12835b01, 0x243185be, 0x550c7dc3, 0x72be5d74, 0x80deb1fe,
0x9bdc06a7, 0xc19bf174, 0xe49b69c1, 0xefbe4786, 0x0fc19dc6, 0x240ca1cc, 0x2de92c6f,
0x4a7484aa, 0x5cb0a9dc, 0x76f988da, 0x983e5152, 0xa831c66d, 0xb00327c8, 0xbf597fc7,
0xc6e00bf3, 0xd5a79147, 0x06ca6351, 0x14292967, 0x27b70a85, 0x2e1b2138, 0x4d2c6dfc,
0x53380d13, 0x650a7354, 0x766a0abb, 0x81c2c92e, 0x92722c85, 0xa2bfe8a1, 0xa81a664b,
0xc24b8b70, 0xc76c51a3, 0xd192e819, 0xd6990624, 0xf40e3585, 0x106aa070, 0x19a4c116,
0x1e376c08, 0x2748774c, 0x34b0bcb5, 0x391c0cb3, 0x4ed8aa4a, 0x5b9cca4f, 0x682e6ff3,
0x748f82ee, 0x78a5636f, 0x84c87814, 0x8cc70208, 0x90befffa, 0xa4506ceb, 0xbef9a3f7,
0xc67178f2,
];
let mut h: [u32; 8] = [
0x6a09e667, 0xbb67ae85, 0x3c6ef372, 0xa54ff53a, 0x510e527f, 0x9b05688c, 0x1f83d9ab,
0x5be0cd19,
];
let mut msg = data.to_vec();
let bits = (data.len() as u64) * 8;
msg.push(0x80);
while msg.len() % 64 != 56 {
msg.push(0);
}
msg.extend_from_slice(&bits.to_be_bytes());
for block in msg.chunks(64) {
let mut w = [0u32; 64];
for i in 0..16 {
w[i] = u32::from_be_bytes([
block[i * 4],
block[i * 4 + 1],
block[i * 4 + 2],
block[i * 4 + 3],
]);
}
for i in 16..64 {
let s0 = w[i - 15].rotate_right(7) ^ w[i - 15].rotate_right(18) ^ (w[i - 15] >> 3);
let s1 = w[i - 2].rotate_right(17) ^ w[i - 2].rotate_right(19) ^ (w[i - 2] >> 10);
w[i] = w[i - 16]
.wrapping_add(s0)
.wrapping_add(w[i - 7])
.wrapping_add(s1);
}
let (mut a, mut b, mut c, mut d, mut e, mut f, mut g, mut hh) =
(h[0], h[1], h[2], h[3], h[4], h[5], h[6], h[7]);
for i in 0..64 {
let s1 = e.rotate_right(6) ^ e.rotate_right(11) ^ e.rotate_right(25);
let ch = (e & f) ^ ((!e) & g);
let t1 = hh
.wrapping_add(s1)
.wrapping_add(ch)
.wrapping_add(K[i])
.wrapping_add(w[i]);
let s0 = a.rotate_right(2) ^ a.rotate_right(13) ^ a.rotate_right(22);
let maj = (a & b) ^ (a & c) ^ (b & c);
let t2 = s0.wrapping_add(maj);
hh = g;
g = f;
f = e;
e = d.wrapping_add(t1);
d = c;
c = b;
b = a;
a = t1.wrapping_add(t2);
}
for (i, v) in [a, b, c, d, e, f, g, hh].iter().enumerate() {
h[i] = h[i].wrapping_add(*v);
}
}
h.iter().map(|x| format!("{x:08x}")).collect()
}
#[cfg(test)]
mod tests {
use super::*;
fn inert() -> Research {
Research::new(Config { base: "http://127.0.0.1:1".into(), ..Default::default() })
}
#[test]
fn an_unconfigured_client_is_inert_not_broken() {
// The property that makes it safe on a production path: with no key, every verb
// returns Ok without touching the network. The base points at a closed port, so a
// real request would error — proving these never dialled.
let c = inert();
assert!(!c.enabled());
assert!(c.query("p", "k").unwrap().is_empty());
assert_eq!(c.totals("p").unwrap().experiments, 0);
assert_eq!(c.grant(GrantRequest::default()).unwrap(), 0);
let mut e = c.experiment("kernel-perf", "gfx1151", "q4k").start().unwrap();
e.record("n=4096", "hand", Attempt::ok("1.0")).unwrap();
e.conclude(Verdict::Proven, "because", 1.0).unwrap();
}
#[test]
fn accuracy_excludes_faulted_arms() {
let c = inert();
let mut e = c.experiment("benchmark", "s", "t").start().unwrap();
e.record("a", "m", Attempt::ok("x")).unwrap();
e.record("b", "m", Attempt::wrong("y", "x")).unwrap();
e.record("c", "m", Attempt::faulted("oom")).unwrap();
assert_eq!(e.accuracy(), 0.5, "2 counted, 1 correct; the faulted arm is excluded");
}
#[test]
fn id_is_the_cross_language_key() {
let c = inert();
let e = c.experiment("kernel-perf", "gfx1151", "q4k-decode").start().unwrap();
assert_eq!(e.id(), "kernel-perf:gfx1151:q4k-decode");
}
#[test]
fn sha256_matches_known_vectors() {
assert_eq!(
sha256_hex(b""),
"e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"
);
assert_eq!(
sha256_hex(b"abc"),
"ba7816bf8f01cfea414140de5dae2223b00361a396177a9cb410ff61f20015ad"
);
}
#[test]
fn base64_matches_known_vectors() {
assert_eq!(b64(b""), "");
assert_eq!(b64(b"f"), "Zg==");
assert_eq!(b64(b"fo"), "Zm8=");
assert_eq!(b64(b"foo"), "Zm9v");
assert_eq!(b64(b"foobar"), "Zm9vYmFy");
}
#[test]
fn marketing_and_kernels_share_one_plane() {
// kind is an open string: the same call shape records an ad test and a kernel A/B.
let c = inert();
let a = c.experiment("marketing-experiment", "launch-email", "subject-line").start().unwrap();
let b = c.experiment("kernel-perf", "gfx1151", "q4k-decode").start().unwrap();
assert_eq!(a.id(), "marketing-experiment:launch-email:subject-line");
assert_eq!(b.id(), "kernel-perf:gfx1151:q4k-decode");
}
}
-136
View File
@@ -1,136 +0,0 @@
// Copyright © 2026 Hanzo AI. MIT License.
//! Zero-config provenance: the commit a result came from, and the box it ran on.
//!
//! Every probe here is best-effort. A missing `git` binary, a non-repo directory or an
//! unreadable environment yields an empty value and never fails an experiment — a result
//! measured without provenance is still a result, and refusing to record it would lose it.
use std::process::Command;
use std::time::Duration;
/// The commit an experiment was measured at.
///
/// `dirty` is load-bearing, not decorative: a number measured on an uncommitted tree is
/// not reproducible, and the plane records that fact rather than implying otherwise.
#[derive(Debug, Clone, Default)]
pub struct Vcs {
pub sha: String,
pub branch: String,
pub dirty: bool,
}
/// The machine a run executed on — enough to correlate a result to hardware.
///
/// A kernel number is meaningless without it: 1.8x on GB10 and 1.8x on a laptop are
/// different claims.
#[derive(Debug, Clone, Default, serde::Serialize, serde::Deserialize)]
pub struct Host {
pub hostname: String,
pub platform: String,
}
const GIT_TIMEOUT: Duration = Duration::from_secs(5);
/// Runs `git -C repo args...` and returns trimmed stdout, or an empty string on any error.
fn git(repo: &str, args: &[&str]) -> String {
let mut cmd = Command::new("git");
cmd.arg("-C").arg(repo).args(args);
// std::process has no timeout; the git probes below are all local metadata reads that
// return immediately. The constant documents the intended bound for a future async port.
let _ = GIT_TIMEOUT;
match cmd.output() {
Ok(out) if out.status.success() => String::from_utf8_lossy(&out.stdout).trim().to_string(),
_ => String::new(),
}
}
impl Vcs {
/// Reads HEAD's sha, branch and dirtiness for `repo`.
pub fn detect(repo: &str) -> Self {
Vcs {
sha: git(repo, &["rev-parse", "HEAD"]),
branch: git(repo, &["rev-parse", "--abbrev-ref", "HEAD"]),
dirty: !git(repo, &["status", "--porcelain"]).is_empty(),
}
}
}
impl Host {
/// Reads the hostname and platform of the current box.
pub fn detect() -> Self {
let hostname = std::env::var("HOSTNAME")
.ok()
.filter(|h| !h.trim().is_empty())
.unwrap_or_else(|| {
match Command::new("hostname").output() {
Ok(o) if o.status.success() => {
String::from_utf8_lossy(&o.stdout).trim().to_string()
}
_ => String::new(),
}
});
Host {
hostname,
platform: format!("{}/{}", std::env::consts::OS, std::env::consts::ARCH),
}
}
}
/// Walks up from `start` to the nearest directory containing `.git`.
///
/// Falls back to the current directory so provenance capture is never a hard failure.
pub fn find_repo(start: &str) -> String {
let mut dir = if start.is_empty() {
std::env::current_dir().unwrap_or_default()
} else {
std::path::PathBuf::from(start)
};
loop {
if dir.join(".git").exists() {
return dir.to_string_lossy().into_owned();
}
if !dir.pop() {
return std::env::current_dir()
.unwrap_or_default()
.to_string_lossy()
.into_owned();
}
}
}
/// Resolves `name` to a version string from the environment.
///
/// A harness records the versions that actually shaped the number — the quant library,
/// the driver, the model revision — under keys it chooses.
pub fn lib_version(name: &str) -> Option<String> {
let key = format!("{}_VERSION", name.to_uppercase().replace('-', "_"));
std::env::var(key).ok().filter(|v| !v.trim().is_empty())
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn probes_never_panic_off_a_repo() {
// The contract that matters: provenance is best-effort. Pointing every probe at a
// path that is definitely not a git repo must yield empties, not a panic or an error.
let v = Vcs::detect("/definitely/not/a/repo/9137");
assert!(v.sha.is_empty());
assert!(v.branch.is_empty());
assert!(!v.dirty);
}
#[test]
fn host_always_reports_a_platform() {
let h = Host::detect();
assert!(h.platform.contains('/'), "platform is os/arch");
}
#[test]
fn find_repo_falls_back_rather_than_failing() {
let r = find_repo("/definitely/not/a/repo/9137");
assert!(!r.is_empty());
}
}