sdk

Radroots SDK and bindings
git clone https://radroots.dev/git/sdk.git
Log | Files | Refs | README

commit 8983bedf79f8b558dee05e643eff61fa54746271
parent c9fde521b1640fe7ecbbdd6449a192d8f861f33d
Author: triesap <tyson@radroots.org>
Date:   Mon,  3 Aug 2026 13:31:44 +0000

wasm: migrate deterministic event codec

- Bind generated WASM artifacts to exact package, crate, target, and source identities.
- Hash every JavaScript, declaration, and WebAssembly output deterministically.
- Record deterministic-codec capability while excluding networking and signing.
- Validate provenance, wasm32 compilation, conformance vectors, and npm payloads.

Diffstat:
Mtools/xtask/src/check.rs | 3+++
Mtools/xtask/src/wasm.rs | 152++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++-
2 files changed, 154 insertions(+), 1 deletion(-)

diff --git a/tools/xtask/src/check.rs b/tools/xtask/src/check.rs @@ -21,6 +21,7 @@ use crate::{ }, package_metadata::{PACKAGE_FILES, check_package_distribution_metadata, package_description}, ts::generated_header, + wasm::validate_provenance_manifest, wasm_declarations::declaration_files, }; @@ -1230,6 +1231,7 @@ pub(crate) fn check_wasm_package_surface(root: &Path, spec: WasmPackageSpec) -> check_package_surface_artifacts(&package_dir, spec.package_name, &surface_paths)?; check_wasm_runtime_files(&package_dir, spec)?; check_wasm_declaration_files(&package_dir, spec)?; + validate_provenance_manifest(root, spec)?; Ok(()) } @@ -1543,6 +1545,7 @@ fn expected_packed_dist_files( for path in wasm_runtime_files(spec) { expected.insert(path); } + expected.insert(format!("dist/{}.provenance.json", spec.out_name)); } else { expected.extend(expected_binding_generated_dist_files(package_dir)?); } diff --git a/tools/xtask/src/wasm.rs b/tools/xtask/src/wasm.rs @@ -1,5 +1,5 @@ use std::{ - collections::BTreeSet, + collections::{BTreeMap, BTreeSet}, env, ffi::OsStr, fs, io, @@ -7,6 +7,9 @@ use std::{ process::{Command, Stdio}, }; +use serde::{Deserialize, Serialize}; +use sha2::{Digest, Sha256}; + use crate::{ check::check_wasm_package_surface, fs::workspace_root, @@ -76,12 +79,159 @@ pub fn generate(args: &[String]) -> Result<(), String> { } remove_wasm_pack_gitignore(&dist_dir, spec)?; write_declaration_files(&root, spec)?; + write_provenance_manifest(&root, spec)?; check_wasm_package_surface(&root, spec)?; println!("generated wasm package {}", spec.package_name); } Ok(()) } +#[derive(Debug, Deserialize, Eq, PartialEq, Serialize)] +struct WasmProvenanceManifest { + schema_version: u16, + package: String, + package_version: String, + rust_crate: String, + rust_crate_version: String, + target: String, + deterministic_codec: bool, + networking: bool, + signing: bool, + source_sha256: String, + outputs: BTreeMap<String, String>, +} + +fn write_provenance_manifest(root: &Path, spec: WasmPackageSpec) -> Result<(), String> { + let manifest = provenance_manifest(root, spec)?; + let dist_dir = root.join(spec.package_dir).join("dist"); + let path = provenance_manifest_path(&dist_dir, spec); + let mut rendered = serde_json::to_string_pretty(&manifest) + .map_err(|error| format!("failed to render {}: {error}", path.display()))?; + rendered.push('\n'); + fs::write(&path, rendered) + .map_err(|error| format!("failed to write {}: {error}", path.display())) +} + +fn provenance_manifest( + root: &Path, + spec: WasmPackageSpec, +) -> Result<WasmProvenanceManifest, String> { + let package_version = manifest_version(&root.join(spec.package_dir).join("package.json"))?; + let rust_crate_version = manifest_version(&root.join(spec.crate_dir).join("Cargo.toml"))?; + let source_sha256 = source_tree_sha256(root, spec)?; + let dist_dir = root.join(spec.package_dir).join("dist"); + let mut outputs = BTreeMap::new(); + for relative in wasm_output_files(spec) { + let path = dist_dir.join(&relative); + let bytes = fs::read(&path) + .map_err(|error| format!("failed to read {}: {error}", path.display()))?; + outputs.insert(relative, format!("{:x}", Sha256::digest(bytes))); + } + Ok(WasmProvenanceManifest { + schema_version: 1, + package: spec.package_name.to_owned(), + package_version, + rust_crate: spec.crate_name.to_owned(), + rust_crate_version, + target: WASM_TARGET.to_owned(), + deterministic_codec: spec.key == "event_codec", + networking: false, + signing: false, + source_sha256, + outputs, + }) +} + +pub(crate) fn validate_provenance_manifest( + root: &Path, + spec: WasmPackageSpec, +) -> Result<(), String> { + let path = provenance_manifest_path(&root.join(spec.package_dir).join("dist"), spec); + let raw = fs::read_to_string(&path) + .map_err(|error| format!("failed to read {}: {error}", path.display()))?; + let actual: WasmProvenanceManifest = serde_json::from_str(&raw) + .map_err(|error| format!("failed to parse {}: {error}", path.display()))?; + let expected = provenance_manifest(root, spec)?; + if actual != expected { + return Err(format!( + "stale or invalid WASM provenance manifest: {}", + path.display() + )); + } + Ok(()) +} + +fn manifest_version(path: &Path) -> Result<String, String> { + let raw = fs::read_to_string(path) + .map_err(|error| format!("failed to read {}: {error}", path.display()))?; + if path.extension() == Some(OsStr::new("json")) { + let value: serde_json::Value = serde_json::from_str(&raw) + .map_err(|error| format!("failed to parse {}: {error}", path.display()))?; + return value["version"] + .as_str() + .map(str::to_owned) + .ok_or_else(|| format!("{} must define string version", path.display())); + } + let value: toml::Value = raw + .parse() + .map_err(|error| format!("failed to parse {}: {error}", path.display()))?; + value["package"]["version"] + .as_str() + .map(str::to_owned) + .ok_or_else(|| format!("{} must define explicit package.version", path.display())) +} + +fn source_tree_sha256(root: &Path, spec: WasmPackageSpec) -> Result<String, String> { + let crate_dir = root.join(spec.crate_dir); + let mut files = vec![crate_dir.join("Cargo.toml")]; + collect_source_files(&crate_dir.join("src"), &mut files)?; + files.sort(); + let mut hasher = Sha256::new(); + for path in files { + let relative = path + .strip_prefix(root) + .map_err(|error| format!("failed to relativize {}: {error}", path.display()))?; + hasher.update(relative.to_string_lossy().as_bytes()); + hasher.update([0]); + hasher.update( + fs::read(&path) + .map_err(|error| format!("failed to read {}: {error}", path.display()))?, + ); + hasher.update([0]); + } + Ok(format!("{:x}", hasher.finalize())) +} + +fn collect_source_files(dir: &Path, files: &mut Vec<PathBuf>) -> Result<(), String> { + let mut entries = fs::read_dir(dir) + .map_err(|error| format!("failed to read {}: {error}", dir.display()))? + .collect::<Result<Vec<_>, _>>() + .map_err(|error| format!("failed to enumerate {}: {error}", dir.display()))?; + entries.sort_by_key(std::fs::DirEntry::file_name); + for entry in entries { + let path = entry.path(); + if path.is_dir() { + collect_source_files(&path, files)?; + } else if path.is_file() { + files.push(path); + } + } + Ok(()) +} + +fn wasm_output_files(spec: WasmPackageSpec) -> [String; 4] { + [ + format!("{}.js", spec.out_name), + format!("{}.d.ts", spec.out_name), + format!("{}_bg.wasm", spec.out_name), + format!("{}_bg.wasm.d.ts", spec.out_name), + ] +} + +fn provenance_manifest_path(dist_dir: &Path, spec: WasmPackageSpec) -> PathBuf { + dist_dir.join(format!("{}.provenance.json", spec.out_name)) +} + struct WasmToolchain { wasm_pack: PathBuf, rust: ResolvedRustToolchain,