From 9aa58d3671fe084cb9e92f219b8e2590b810b89a Mon Sep 17 00:00:00 2001 From: Noah Gift Date: Wed, 2 Sep 2026 10:39:59 +0200 Subject: [PATCH 1/3] fix(aprender-gpu): drop the manzana dependency and its dead Metal backend manzana 0.1.0 and 0.2.0 are yanked (RUSTSEC-2026-0273), and the advisory still carries `patched = []`, so no bump clears it. This removes the dependency instead -- which is the condition `.cargo/audit.toml` already named for retiring its own ignore entry: REMOVE WHEN either the advisory gains a patched range and this workspace is on a version inside it, or the manzana dependency is dropped from aprender-gpu entirely. So the ignore goes too (35 lines). `cargo audit` now exits 0 with RUSTSEC-2026-0273 not merely suppressed but absent: manzana is gone from Cargo.lock, which is the only place it ever appeared unconditionally. Nothing is lost. The `metal` feature was never enabled anywhere -- not in `default`, not in another manifest, not in CI -- so every line behind it was dead. The two manzana calls it gated only ever *enumerated* devices; the operations that would make a Metal backend useful were fabricated upstream (`dispatch()` returned `Ok(())` having dispatched nothing, `compile_shader()` returned a handle built from a hash of the source string). manzana 0.3.x replaces both with `Err(Unimplemented)`, so the backend cannot do real work on any published version. `MetalBackend` now reports unavailable unconditionally, matching `VulkanBackend`, which has always been an honest placeholder. On macOS the `wgpu` feature already reaches Apple GPUs through wgpu's Metal backend and does execute. Removed `tests/metal_backend_f101.rs` (359 lines, 10 `#[test]` fns). Nine opened with `if !metal_available() { return }` and the tenth asserted only `!available` off-macOS; `metal_available()` was hardwired to `false` whenever the feature was off -- which was always. The suite has never asserted anything on any machine, while reporting 10 passing tests. `metal_shaders` is now compiled unconditionally rather than behind the removed feature. It is MSL source strings with no manzana dependency, and its 4 tests now actually run: previously they were gated on `macos + metal` and so were never built. Its doc example, which called the now-absent `MetalCompute::default_device()`, says plainly that this crate has no dispatcher. Verified: cargo check -p aprender-gpu clean; cargo test -p aprender-gpu --lib 444 passed / 0 failed / 0 ignored; cargo test -p aprender-contracts --lib 1474 passed / 0 failed; cargo audit exit 0; cargo deny check advisories ok; rustfmt clean on changed files. The `useless use of vec!` clippy warnings are in tests/rocm_backend_f111.rs, untouched here and pre-existing. Not verified: the macOS build. This toolchain has no std for aarch64-apple-darwin. The change removes platform-gated code rather than adding any, and no `cfg(target_os)` arm remains in the touched files. Co-Authored-By: Claude Opus 5 (1M context) Claude-Session: https://claude.ai/code/session_01RnES3PSMkcR9tc2iP3sZQg --- .cargo/audit.toml | 35 -- Cargo.lock | 15 - crates/aprender-gpu/Cargo.toml | 6 - .../aprender-gpu/src/backend/metal_shaders.rs | 10 +- crates/aprender-gpu/src/backend/mod.rs | 30 +- crates/aprender-gpu/src/backend/tests.rs | 36 +- .../aprender-gpu/tests/metal_backend_f101.rs | 359 ------------------ 7 files changed, 19 insertions(+), 472 deletions(-) delete mode 100644 crates/aprender-gpu/tests/metal_backend_f101.rs diff --git a/.cargo/audit.toml b/.cargo/audit.toml index b8e5bfefcc..dbc4ee6b11 100644 --- a/.cargo/audit.toml +++ b/.cargo/audit.toml @@ -137,39 +137,4 @@ ignore = [ # cargo tree --workspace | grep -cE "h2 v0.3" -> 0 # REMOVE WHEN aws-smithy-http-client drops hyper 0.14. "RUSTSEC-2026-0258", # h2 0.3.27 via optional s3 feature only - - # manzana 0.2.0 stubbed cryptography. CONTAINMENT ONLY - remove per the - # condition at the bottom of this block, which is NOT "when a new manzana - # ships". - # - # The advisory is about `manzana::secure_enclave::SecureEnclaveSigner`, whose - # sign() derives a "signature" from public values only and whose verify() - # merely recomputes sign() and compares. aprender does not touch any of it. - # Reachability measured on a clean origin/main worktree, x86_64 Linux, using - # the absolute cargo binary (a `cargo` shell function on the dev box shadows - # it and silently redirects CARGO_TARGET_DIR): - # cargo tree -p aprender-gpu | grep -c manzana -> 0 - # cargo tree -p aprender-gpu --features metal | grep -c manzana -> 0 - # cargo tree --workspace | grep -c manzana -> 0 - # git grep -l 'secure_enclave|SecureEnclave' -- '*.rs' -> 0 files - # The middle line is the load-bearing one: manzana is BOTH optional and - # declared under [target.'cfg(target_os = "macos")'.dependencies], so - # enabling `metal` on Linux still pulls in nothing. aprender's only three - # call sites are `manzana::metal::*` in aprender-gpu/src/backend/mod.rs, - # every one behind cfg(all(target_os = "macos", feature = "metal")). - # As with the h2 entry above, cargo-deny already passes without an exemption - # because it walks the ACTIVATED graph; cargo-audit scans Cargo.lock, which - # lists target- and feature-gated deps unconditionally. - # - # WHY AN IGNORE RATHER THAN AN UPGRADE. Both published versions (0.1.0 and - # 0.2.0) are YANKED, so there is nothing to bump to - `max_version` on - # crates.io reads 0.0.0. More importantly the advisory carries - # `[versions] patched = []`, which means NO version is considered fixed: - # publishing a corrected manzana does NOT clear this gate on its own. Only - # amending the upstream advisory to name a patched range does. - # - # REMOVE WHEN either the advisory gains a patched range and this workspace is - # on a version inside it, or the manzana dependency is dropped from - # aprender-gpu entirely. Publishing a new manzana alone is NOT the condition. - "RUSTSEC-2026-0273", # manzana 0.2.0 stubbed crypto; unreachable here, see above ] diff --git a/Cargo.lock b/Cargo.lock index 12b2b3049a..3b736286cd 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -780,7 +780,6 @@ dependencies = [ "criterion 0.7.0", "crossterm 0.28.1", "libloading", - "manzana", "pollster", "proptest", "thiserror 2.0.18", @@ -8121,20 +8120,6 @@ dependencies = [ "libc", ] -[[package]] -name = "manzana" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4b5eed7c777f2d9d8313bdbc671c19fae34769646b42e9ea2beeec9f5d872cb7" -dependencies = [ - "bitflags 2.13.0", - "core-foundation 0.10.1", - "core-foundation-sys", - "mach2", - "thiserror 1.0.69", - "tracing", -] - [[package]] name = "matchers" version = "0.2.0" diff --git a/crates/aprender-gpu/Cargo.toml b/crates/aprender-gpu/Cargo.toml index 465f67d30c..8c5743ff9e 100644 --- a/crates/aprender-gpu/Cargo.toml +++ b/crates/aprender-gpu/Cargo.toml @@ -41,10 +41,6 @@ crossterm = { version = "0.28", optional = true } # WGPU for cross-platform WebGPU compute (Vulkan/Metal/DX12/WebGPU) wgpu = { version = "24", optional = true } -# Apple Metal backend via manzana (macOS only) -[target.'cfg(target_os = "macos")'.dependencies] -manzana = { version = "0.2.0", optional = true } - [dev-dependencies] proptest = "1.9" criterion = { workspace = true } @@ -81,8 +77,6 @@ tui-monitor = ["stress-test", "dep:crossterm"] gpu-pixels = ["dep:crossterm"] # WGPU backend for cross-platform GPU compute (WebGPU via wgpu crate) wgpu = ["dep:wgpu"] -# Apple Metal backend via manzana (macOS only) -metal = ["dep:manzana"] [package.metadata.wasm-pack.profile.release] wasm-opt = false diff --git a/crates/aprender-gpu/src/backend/metal_shaders.rs b/crates/aprender-gpu/src/backend/metal_shaders.rs index cef99396c9..4ee2b9cb1d 100644 --- a/crates/aprender-gpu/src/backend/metal_shaders.rs +++ b/crates/aprender-gpu/src/backend/metal_shaders.rs @@ -5,11 +5,15 @@ //! //! # Usage //! +//! These are source strings only. This crate contains no Metal dispatcher, so +//! nothing here compiles or runs them -- pass a constant to a Metal API of your +//! own (`MTLDevice::newLibraryWithSource`), or use the `wgpu` feature, which +//! reaches Apple GPUs through wgpu's Metal backend and does execute. +//! //! ```ignore -//! use trueno_gpu::backend::metal_shaders; +//! use aprender_gpu::backend::metal_shaders; //! -//! let compute = MetalCompute::default_device()?; -//! let shader = compute.compile_shader(metal_shaders::ELEMENTWISE_ADD, "elementwise_add")?; +//! let msl: &str = metal_shaders::ELEMENTWISE_ADD; // kernel name: "elementwise_add" //! ``` /// Element-wise vector addition kernel diff --git a/crates/aprender-gpu/src/backend/mod.rs b/crates/aprender-gpu/src/backend/mod.rs index 15562f46b0..5377187673 100644 --- a/crates/aprender-gpu/src/backend/mod.rs +++ b/crates/aprender-gpu/src/backend/mod.rs @@ -3,10 +3,9 @@ //! Provides a unified interface for different GPU backends: //! - CUDA (NVIDIA) - Primary, uses PTX //! - WGPU (WebGPU) - Cross-platform, uses WGSL (Vulkan/Metal/DX12/WebGPU) -//! - Metal (Apple) - Native Apple GPU compute via manzana crate +//! - Metal (Apple) - shader source only; no dispatcher (see `metal_shaders`) //! - Vulkan (cross-platform, future) -#[cfg(all(target_os = "macos", feature = "metal"))] pub mod metal_shaders; /// Backend trait for GPU operations @@ -57,10 +56,13 @@ impl Backend for CudaBackend { } } -/// Metal backend (Apple GPUs) +/// Metal backend (Apple GPUs) - placeholder /// -/// Uses manzana crate for safe Rust Metal bindings on macOS. -/// Enable with `--features metal` on macOS. +/// Reports unavailable on every platform. This crate contains Metal shader +/// source (`metal_shaders`) but no dispatcher: nothing here calls +/// `MTLDevice::newLibraryWithSource` or `MTLComputeCommandEncoder`. On macOS, +/// use the `wgpu` feature, which reaches Apple GPUs through wgpu's Metal +/// backend and does execute. #[derive(Debug, Default)] pub struct MetalBackend; @@ -69,31 +71,15 @@ impl Backend for MetalBackend { "Metal" } - #[cfg(all(target_os = "macos", feature = "metal"))] fn is_available(&self) -> bool { - manzana::metal::is_available() + false // No dispatcher; see struct docs. } - #[cfg(not(all(target_os = "macos", feature = "metal")))] - fn is_available(&self) -> bool { - false - } - - #[cfg(all(target_os = "macos", feature = "metal"))] - fn device_count(&self) -> usize { - manzana::metal::MetalCompute::devices().len() - } - - #[cfg(not(all(target_os = "macos", feature = "metal")))] fn device_count(&self) -> usize { 0 } } -/// Metal device information (re-exported from manzana when feature enabled) -#[cfg(all(target_os = "macos", feature = "metal"))] -pub use manzana::metal::{CompiledShader as MetalShader, MetalBuffer, MetalCompute, MetalDevice}; - /// Vulkan backend (cross-platform) - placeholder #[derive(Debug, Default)] pub struct VulkanBackend; diff --git a/crates/aprender-gpu/src/backend/tests.rs b/crates/aprender-gpu/src/backend/tests.rs index c3aa050cd3..99b9943998 100644 --- a/crates/aprender-gpu/src/backend/tests.rs +++ b/crates/aprender-gpu/src/backend/tests.rs @@ -7,24 +7,11 @@ fn test_cuda_backend_name() { } #[test] -#[cfg(not(all(target_os = "macos", feature = "metal")))] fn test_metal_backend_unavailable() { let backend = MetalBackend; assert!(!backend.is_available()); } -#[test] -#[cfg(all(target_os = "macos", feature = "metal"))] -fn test_metal_backend_available() { - let backend = MetalBackend; - // On macOS with metal feature, should detect GPUs - assert!(backend.is_available(), "Metal should be available on macOS"); - assert!( - backend.device_count() > 0, - "Should have at least one Metal device" - ); -} - #[test] fn test_detect_backend() { let backend = detect_backend(); @@ -60,23 +47,11 @@ fn test_cuda_backend_device_count() { } #[test] -#[cfg(not(all(target_os = "macos", feature = "metal")))] fn test_metal_backend_device_count() { let backend = MetalBackend; assert_eq!(backend.device_count(), 0); } -#[test] -#[cfg(all(target_os = "macos", feature = "metal"))] -fn test_metal_backend_device_count_macos() { - let backend = MetalBackend; - // On macOS with metal feature, should have at least 1 GPU - assert!( - backend.device_count() >= 1, - "Should have at least one Metal device" - ); -} - #[test] fn test_vulkan_backend_device_count() { let backend = VulkanBackend; @@ -286,13 +261,10 @@ fn test_detect_backend_wgpu_priority_over_metal_and_vulkan() { #[test] fn test_detect_backend_metal_not_returned_on_linux() { - // On Linux (non-macOS without metal feature), Metal should never be returned - #[cfg(not(all(target_os = "macos", feature = "metal")))] - { - let metal = MetalBackend; - assert!(!metal.is_available()); - // Metal branch in detect_backend is unreachable on Linux - } + // MetalBackend has no dispatcher, so it never reports available and the + // Metal branch in detect_backend is unreachable on every platform. + let metal = MetalBackend; + assert!(!metal.is_available()); } #[test] diff --git a/crates/aprender-gpu/tests/metal_backend_f101.rs b/crates/aprender-gpu/tests/metal_backend_f101.rs deleted file mode 100644 index 48f9aa8b9d..0000000000 --- a/crates/aprender-gpu/tests/metal_backend_f101.rs +++ /dev/null @@ -1,359 +0,0 @@ -//! PMAT-006: Apple Silicon Metal Backend Tests (METAL-01 to METAL-05) -//! -//! Falsification tests per FKR-011 specification. -//! Verifies Metal backend produces equivalent results to CUDA reference. -//! -//! Citations: -//! - [Apple 2023] "Metal Best Practices Guide" developer.apple.com/metal -//! - [Gaster & Howes 2012] "Heterogeneous Computing with OpenCL" ISBN:978-0-12-387766-6 -//! - [Lopes et al. 2021] "ML Performance on Apple Silicon" arXiv:2110.01599 -//! -//! Note: These tests require Apple Silicon hardware (M1/M2/M3) to run. -//! On non-Apple platforms, tests are skipped. - -/// Check if Metal backend is available (macOS with Metal feature) -fn metal_available() -> bool { - #[cfg(all(target_os = "macos", feature = "metal"))] - { - use trueno_gpu::backend::{Backend, MetalBackend}; - MetalBackend.is_available() - } - #[cfg(not(all(target_os = "macos", feature = "metal")))] - { - false - } -} - -/// METAL-01: Metal backend compiles on macOS 13+ -/// -/// Hypothesis: Metal compute shaders compile without errors. -/// Falsification: Any shader compilation error on supported macOS. -#[test] -fn metal_01_backend_compiles() { - if !metal_available() { - eprintln!("METAL-01 SKIPPED: Metal not available on this platform"); - return; - } - - // When Metal is available, verify wgpu can create a Metal adapter - // This is a compile-time check - if this test runs, Metal SDK is present - println!("METAL-01 PASSED: Metal backend compilation verified"); -} - -/// METAL-02: All backend equivalence tests pass (<1e-5 tolerance) -/// -/// Hypothesis: Metal produces numerically equivalent results to reference. -/// Falsification: Any result differs by >=1e-5 from CUDA/CPU reference. -#[test] -fn metal_02_equivalence_tolerance() { - if !metal_available() { - eprintln!("METAL-02 SKIPPED: Metal not available on this platform"); - return; - } - - // Test vector addition equivalence - let a = vec![1.0f32, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0]; - let b = vec![8.0f32, 7.0, 6.0, 5.0, 4.0, 3.0, 2.0, 1.0]; - let expected: Vec = a.iter().zip(&b).map(|(x, y)| x + y).collect(); - - // Placeholder: In full implementation, this would use Metal backend - let result = expected.clone(); // Stub: use reference as result - - for (i, (r, e)) in result.iter().zip(&expected).enumerate() { - assert!( - (r - e).abs() < 1e-5, - "METAL-02 FALSIFIED: Element {} differs: {} vs {} (diff={})", - i, - r, - e, - (r - e).abs() - ); - } - - println!("METAL-02 PASSED: Backend equivalence within <1e-5 tolerance"); -} - -/// METAL-03: Performance within 80% of CUDA equivalent -/// -/// Hypothesis: Metal achieves at least 80% of CUDA performance on equivalent ops. -/// Falsification: Metal <80% of CUDA performance on any benchmark. -#[test] -fn metal_03_performance_target() { - if !metal_available() { - eprintln!("METAL-03 SKIPPED: Metal not available on this platform"); - return; - } - - // Performance benchmarks require actual Metal hardware - // This test verifies the performance measurement infrastructure exists - - // Stub: Define performance threshold - const PERFORMANCE_THRESHOLD: f64 = 0.80; // 80% of reference - - // In full implementation, measure actual Metal vs reference performance - let metal_gflops = 100.0; // Placeholder - let reference_gflops = 100.0; // Placeholder (would be CUDA or CPU optimized) - - let performance_ratio = metal_gflops / reference_gflops; - - assert!( - performance_ratio >= PERFORMANCE_THRESHOLD, - "METAL-03 FALSIFIED: Metal performance ratio {} < {} threshold", - performance_ratio, - PERFORMANCE_THRESHOLD - ); - - println!( - "METAL-03 PASSED: Performance ratio {:.1}% >= {:.1}% threshold", - performance_ratio * 100.0, - PERFORMANCE_THRESHOLD * 100.0 - ); -} - -/// METAL-04: Unified memory eliminates explicit transfers -/// -/// Hypothesis: Apple Silicon unified memory avoids CPU-GPU copies. -/// Falsification: Explicit memcpy detected in Metal path. -/// -/// Note: This test checks for unified memory capability. -/// - Apple Silicon (M1/M2/M3): Always has unified memory -/// - Intel Macs with discrete GPUs: Do NOT have unified memory -/// -/// Both configurations are valid - the test verifies correct detection. -#[test] -fn metal_04_unified_memory() { - if !metal_available() { - eprintln!("METAL-04 SKIPPED: Metal not available on this platform"); - return; - } - - // Check unified memory using manzana when available - #[cfg(all(target_os = "macos", feature = "metal"))] - { - use trueno_gpu::backend::MetalCompute; - - let devices = MetalCompute::devices(); - if devices.is_empty() { - eprintln!("METAL-04 SKIPPED: No Metal devices found"); - return; - } - - let first_device = &devices[0]; - let has_unified = first_device.has_unified_memory; - - if has_unified { - println!("METAL-04 PASSED: Unified memory detected (Apple Silicon)"); - } else { - // Intel Macs with discrete GPUs don't have unified memory - // This is expected behavior, not a failure - println!( - "METAL-04 INFO: Discrete GPU detected ({}), no unified memory", - first_device.name - ); - println!("METAL-04 PASSED: Memory architecture correctly identified"); - } - } - - #[cfg(not(all(target_os = "macos", feature = "metal")))] - { - println!("METAL-04 SKIPPED: Metal feature not enabled"); - } -} - -/// METAL-05: Shader compilation cached for fast startup -/// -/// Hypothesis: Second kernel launch is faster due to shader cache. -/// Falsification: No speedup observed on second launch. -#[test] -fn metal_05_shader_cache() { - if !metal_available() { - eprintln!("METAL-05 SKIPPED: Metal not available on this platform"); - return; - } - - use std::time::Instant; - - // Stub: Simulate shader cache behavior - let first_launch = Instant::now(); - std::thread::sleep(std::time::Duration::from_millis(10)); // Simulate compilation - let first_duration = first_launch.elapsed(); - - let second_launch = Instant::now(); - std::thread::sleep(std::time::Duration::from_millis(1)); // Simulate cached launch - let second_duration = second_launch.elapsed(); - - // Second launch should be faster due to shader cache - assert!( - second_duration < first_duration, - "METAL-05 FALSIFIED: Second launch ({:?}) not faster than first ({:?})", - second_duration, - first_duration - ); - - println!( - "METAL-05 PASSED: Shader cache effective (first={:?}, second={:?})", - first_duration, second_duration - ); -} - -/// Test GEMM output vs reference implementation -#[test] -fn test_metal_gemm_equivalence() { - if !metal_available() { - eprintln!("Metal GEMM test SKIPPED: Metal not available"); - return; - } - - // Simple 2x2 matmul reference - let _a = vec![1.0f32, 2.0, 3.0, 4.0]; - let _b = vec![5.0f32, 6.0, 7.0, 8.0]; - let expected = vec![19.0f32, 22.0, 43.0, 50.0]; // A @ B - - // Stub: Use reference as result (actual impl would use Metal with _a, _b) - let result = expected.clone(); - - for (i, (r, e)) in result.iter().zip(&expected).enumerate() { - assert!( - (r - e).abs() < 1e-5, - "GEMM mismatch at {}: {} vs {}", - i, - r, - e - ); - } - - println!("Metal GEMM equivalence verified"); -} - -/// Test softmax output vs reference implementation -#[test] -fn test_metal_softmax_equivalence() { - if !metal_available() { - eprintln!("Metal softmax test SKIPPED: Metal not available"); - return; - } - - let input = vec![1.0f32, 2.0, 3.0, 4.0]; - - // Compute reference softmax - let max_val = input.iter().cloned().fold(f32::NEG_INFINITY, f32::max); - let exp_sum: f32 = input.iter().map(|x| (x - max_val).exp()).sum(); - let expected: Vec = input - .iter() - .map(|x| (x - max_val).exp() / exp_sum) - .collect(); - - // Stub: Use reference as result - let result = expected.clone(); - - // Verify sum to 1 - let sum: f32 = result.iter().sum(); - assert!( - (sum - 1.0).abs() < 1e-5, - "Softmax sum should be 1.0, got {}", - sum - ); - - for (i, (r, e)) in result.iter().zip(&expected).enumerate() { - assert!( - (r - e).abs() < 1e-5, - "Softmax mismatch at {}: {} vs {}", - i, - r, - e - ); - } - - println!("Metal softmax equivalence verified"); -} - -/// Test LayerNorm output vs reference implementation -#[test] -fn test_metal_layernorm_equivalence() { - if !metal_available() { - eprintln!("Metal LayerNorm test SKIPPED: Metal not available"); - return; - } - - let input = vec![1.0f32, 2.0, 3.0, 4.0]; - let eps = 1e-5f32; - - // Compute reference LayerNorm - let mean: f32 = input.iter().sum::() / input.len() as f32; - let variance: f32 = input.iter().map(|x| (x - mean).powi(2)).sum::() / input.len() as f32; - let std_dev = (variance + eps).sqrt(); - let expected: Vec = input.iter().map(|x| (x - mean) / std_dev).collect(); - - // Stub: Use reference as result - let result = expected.clone(); - - // Verify zero mean (approximately) - let result_mean: f32 = result.iter().sum::() / result.len() as f32; - assert!( - result_mean.abs() < 1e-5, - "LayerNorm mean should be ~0, got {}", - result_mean - ); - - for (i, (r, e)) in result.iter().zip(&expected).enumerate() { - assert!( - (r - e).abs() < 1e-5, - "LayerNorm mismatch at {}: {} vs {}", - i, - r, - e - ); - } - - println!("Metal LayerNorm equivalence verified"); -} - -/// Test attention mechanism output -#[test] -fn test_metal_attention_equivalence() { - if !metal_available() { - eprintln!("Metal attention test SKIPPED: Metal not available"); - return; - } - - // Simplified single-head attention: softmax(Q @ K^T / sqrt(d)) @ V - let seq_len = 4; - let d_model = 2; - - // Q, K, V all same for simplicity - let qkv = vec![1.0f32, 0.0, 0.0, 1.0, 1.0, 1.0, 0.5, 0.5]; - - // This is a simplified test - full implementation would compute actual attention - // For now, verify the test infrastructure exists - assert_eq!(qkv.len(), seq_len * d_model); - - println!("Metal attention infrastructure verified"); -} - -/// Verify Metal backend detection -#[test] -fn test_metal_backend_detection() { - let available = metal_available(); - - #[cfg(target_os = "macos")] - { - // On macOS, Metal should generally be available - println!( - "Metal backend detection: {} (macOS)", - if available { - "available" - } else { - "not available" - } - ); - } - - #[cfg(not(target_os = "macos"))] - { - assert!( - !available, - "Metal should not be available on non-macOS platforms" - ); - println!("Metal backend detection: correctly unavailable (non-macOS)"); - } -} From 96b72745c338cb9ceec0ca28c22724de7cad9650 Mon Sep 17 00:00:00 2001 From: Noah Gift Date: Wed, 2 Sep 2026 11:25:20 +0200 Subject: [PATCH 2/3] =?UTF-8?q?review(2849):=20cross-vendor=20receipt=20?= =?UTF-8?q?=E2=80=94=20DEGRADED,=20three=20advisory=20findings?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The §3.E reviewer is agy pinned to gemini-3.1-pro-high (guard's own --match-arm-e-same-family returns 1 for it, 0 for the two Claude ids agy also offers). It returned agreed_with_author=false and three findings; two of its factual claims were re-verified against the tree before being recorded: - it quotes `let result = expected.clone();` in the deleted test file -- present at lines 60, 213, 247. That is worse than the PR body said: the tests that got past the skip guard asserted a value equals its own clone. - it claims PMAT-006 / METAL-01..05 / FKR-011 tracking is lost with the file -- confirmed at lines 1, 3, 27+. Verdict is DEGRADED, not FINDINGS, and the reason is mechanical: §3.D says `attempted: 0` with `status: consulted` is a vacuous pass, so an unrunnable mutation arm is `unreachable`, and §6 makes any unreachable consultation DEGRADED. cargo mutants tested 0 mutants because `cargo test` fails in the UNMUTATED tree -- the doctest at crates/aprender-gpu/src/lib.rs:12 imports `trueno_gpu::ptx`, a pre-rename path. Reproduced identically on the merge-base f21f437c2, so it is pre-existing and not this PR's doing; it is recorded as `measured` in the SARIF rather than narrated around. CUDA was consulted, not skipped: 5 changed paths matched via the guard's own --match-path, and both queries returned material bearing on PTX/CUDA and nothing bearing on a Metal-only diff. The over-broad trigger is working as documented. Not signed here. `pr-review-sign` holds the secret; this receipt validated ACCEPT under a throwaway keypair first, with the guard's four positive controls firing in the same run. Disclosure recorded in the receipt: the session that ran this skill also authored the diff, which is what attestation_level L1-self means. The independence is agy's, not the orchestrator's. Co-Authored-By: Claude Opus 5 (1M context) Claude-Session: https://claude.ai/code/session_01RnES3PSMkcR9tc2iP3sZQg --- .../findings.sarif | 226 ++++++++++++++++++ .../receipt.intoto.jsonl | 1 + 2 files changed, 227 insertions(+) create mode 100644 evidence/pr-review/2849/9aa58d3671fe084cb9e92f219b8e2590b810b89a/findings.sarif create mode 100644 evidence/pr-review/2849/9aa58d3671fe084cb9e92f219b8e2590b810b89a/receipt.intoto.jsonl diff --git a/evidence/pr-review/2849/9aa58d3671fe084cb9e92f219b8e2590b810b89a/findings.sarif b/evidence/pr-review/2849/9aa58d3671fe084cb9e92f219b8e2590b810b89a/findings.sarif new file mode 100644 index 0000000000..86bd34c786 --- /dev/null +++ b/evidence/pr-review/2849/9aa58d3671fe084cb9e92f219b8e2590b810b89a/findings.sarif @@ -0,0 +1,226 @@ +{ + "$schema": "https://raw.githubusercontent.com/oasis-tcs/sarif-spectool/main/schemas/sarif-schema-2.1.0.json", + "version": "2.1.0", + "runs": [ + { + "tool": { + "driver": { + "name": "pmat", + "version": "cli", + "informationUri": "https://github.com/paiml/paiml-mcp-agent-toolkit", + "rules": [ + { + "id": "pmat/satd", + "name": "satd", + "shortDescription": { + "text": "self-admitted technical debt introduced by this diff" + } + } + ] + } + }, + "invocations": [ + { + "executionSuccessful": true, + "toolExecutionNotifications": [] + } + ], + "results": [] + }, + { + "tool": { + "driver": { + "name": "nvidia-cuda-docs", + "version": "mcp", + "informationUri": "https://docs.nvidia.com/cuda/", + "rules": [ + { + "id": "cuda/applicability", + "name": "applicability", + "shortDescription": { + "text": "CUDA documentation bearing on the diff" + } + } + ] + } + }, + "invocations": [ + { + "executionSuccessful": true, + "toolExecutionNotifications": [] + } + ], + "results": [] + }, + { + "tool": { + "driver": { + "name": "cargo-mutants", + "version": "in-diff", + "informationUri": "https://mutants.rs", + "rules": [ + { + "id": "mutation/baseline", + "name": "baseline", + "shortDescription": { + "text": "the unmutated tree must build before mutants can run" + } + } + ] + } + }, + "invocations": [ + { + "executionSuccessful": false, + "toolExecutionNotifications": [ + { + "level": "error", + "message": { + "text": "cargo mutants tested 0 mutants: `cargo test` fails in the UNMUTATED tree. The doctest at crates/aprender-gpu/src/lib.rs:12 references `trueno_gpu::ptx`, a pre-rename path. Reproduced identically on the merge-base f21f437c2, so it is pre-existing and not introduced by this PR." + }, + "properties": { + "grounding": "measured", + "command": [ + "cargo", + "mutants", + "--in-diff", + "/tmp/prrev/indiff.patch", + "--package", + "aprender-gpu", + "--timeout", + "120", + "--jobs", + "4" + ], + "exit_code": 0, + "stdout_sha256": "8636c7316738a45117b6f762671e78917b38ed38d7f458a7e25825adb27a3508" + } + } + ] + } + ], + "results": [] + }, + { + "tool": { + "driver": { + "name": "antigravity", + "version": "agy", + "informationUri": "https://antigravity.google", + "rules": [ + { + "id": "antigravity/high", + "name": "high", + "shortDescription": { + "text": "cross-vendor reviewer, high severity" + } + }, + { + "id": "antigravity/medium", + "name": "medium", + "shortDescription": { + "text": "cross-vendor reviewer, medium severity" + } + }, + { + "id": "antigravity/low", + "name": "low", + "shortDescription": { + "text": "cross-vendor reviewer, low severity" + } + } + ] + } + }, + "invocations": [ + { + "executionSuccessful": true, + "toolExecutionNotifications": [] + } + ], + "results": [ + { + "ruleId": "antigravity/high", + "level": "error", + "message": { + "text": "SemVer Violation: Removal of public feature and exports \u2014 Removing the `metal` Cargo feature and the `pub use manzana::metal::*` exports (from backend/mod.rs) breaks backward compatibility for any external consumers of the `aprender-gpu` 0.64.0 public API. This requires a version bump (e.g., to 0.65.0) to comply with Cargo's semantic versioning rules, regardless of whether the feature was enabled within this specific workspace." + }, + "locations": [ + { + "physicalLocation": { + "artifactLocation": { + "uri": "crates/aprender-gpu/Cargo.toml" + }, + "region": { + "startLine": 80 + } + } + } + ], + "properties": { + "grounding": "asserted", + "precision_class": "advisory", + "rationale": "Removing the `metal` Cargo feature and the `pub use manzana::metal::*` exports (from backend/mod.rs) breaks backward compatibility for any external consumers of the `aprender-gpu` 0.64.0 public API. This requires a version bump (e.g., to 0.65.0) to comply with Cargo's semantic versioning rules, regardless of whether the feature was enabled within this specific workspace.", + "confidence": "high", + "reviewer": "agy/gemini-3.1-pro-high", + "failure_scenario": "A consumer whose Cargo.toml reads aprender-gpu = { version = \"0.64\", features = [\"metal\"] } resolves against a 0.64.x release carrying this change; cargo halts with `the package aprender-gpu does not have the feature metal`, and no patch-level pin avoids it." + } + }, + { + "ruleId": "antigravity/medium", + "level": "warning", + "message": { + "text": "Process Violation: Loss of PMAT-006 specification traceability \u2014 Although the author is correct that the tests were tautological stubs (e.g., `let result = expected.clone(); assert_eq!(result, expected)`), completely deleting the file removes the PMAT-006 (METAL-01 to METAL-05) falsification tracking. Given the project's strict QA and falsification specification, these requirements should be formally deprecated or the tests rewritten to validate the `wgpu` Metal path, rather than silently dropping the matrix." + }, + "locations": [ + { + "physicalLocation": { + "artifactLocation": { + "uri": "crates/aprender-gpu/tests/metal_backend_f101.rs" + }, + "region": { + "startLine": 1 + } + } + } + ], + "properties": { + "grounding": "asserted", + "precision_class": "advisory", + "rationale": "Although the author is correct that the tests were tautological stubs (e.g., `let result = expected.clone(); assert_eq!(result, expected)`), completely deleting the file removes the PMAT-006 (METAL-01 to METAL-05) falsification tracking. Given the project's strict QA and falsification specification, these requirements should be formally deprecated or the tests rewritten to validate the `wgpu` Metal path, rather than silently dropping the matrix.", + "confidence": "high", + "reviewer": "agy/gemini-3.1-pro-high", + "failure_scenario": "An engineer greps METAL-01..METAL-05 to find what covers Apple GPU inference, finds nothing, and concludes the falsification conditions never existed rather than that they were retired; five FKR-011 conditions leave the matrix with no UNMEASURED or NOT_APPLICABLE record of their removal." + } + }, + { + "ruleId": "antigravity/low", + "level": "note", + "message": { + "text": "Binary bloat on non-Apple platforms from unconditional shaders \u2014 Removing the `#[cfg(all(target_os = \"macos\", feature = \"metal\"))]` gate exposes `pub mod metal_shaders;` unconditionally. This embeds Apple-specific Metal shader string literals into the compiled binaries for Linux and Windows targets where they are completely unusable, introducing minor binary bloat and potential dead-code issues." + }, + "locations": [ + { + "physicalLocation": { + "artifactLocation": { + "uri": "crates/aprender-gpu/src/backend/mod.rs" + }, + "region": { + "startLine": 7 + } + } + } + ], + "properties": { + "grounding": "asserted", + "precision_class": "advisory", + "rationale": "Removing the `#[cfg(all(target_os = \"macos\", feature = \"metal\"))]` gate exposes `pub mod metal_shaders;` unconditionally. This embeds Apple-specific Metal shader string literals into the compiled binaries for Linux and Windows targets where they are completely unusable, introducing minor binary bloat and potential dead-code issues.", + "confidence": "high", + "reviewer": "agy/gemini-3.1-pro-high", + "failure_scenario": "A Linux or Windows build of aprender-gpu links 13 MSL shader string constants that no code path on those targets can dispatch, placing unreachable Apple-specific source in .rodata of every non-Apple binary." + } + } + ] + } + ] +} diff --git a/evidence/pr-review/2849/9aa58d3671fe084cb9e92f219b8e2590b810b89a/receipt.intoto.jsonl b/evidence/pr-review/2849/9aa58d3671fe084cb9e92f219b8e2590b810b89a/receipt.intoto.jsonl new file mode 100644 index 0000000000..f0c08aef86 --- /dev/null +++ b/evidence/pr-review/2849/9aa58d3671fe084cb9e92f219b8e2590b810b89a/receipt.intoto.jsonl @@ -0,0 +1 @@ +{"_type":"https://in-toto.io/Statement/v1","subject":[{"name":"git+https://github.com/paiml/aprender","digest":{"sha1":"9aa58d3671fe084cb9e92f219b8e2590b810b89a"}}],"predicateType":"https://paiml.dev/attestations/pr-review/v2","predicate":{"skill_version":"2.1.0","attestation_level":"L1-self","pr":2849,"base_sha":"f21f437c2ee9e9a00b77f91a87cd38fc5bef60cc","head_sha":"9aa58d3671fe084cb9e92f219b8e2590b810b89a","author_actor":{"kind":"agent","id":"agent:claude-opus-5/manzana-advisory-remediation"},"reviewer_actor":{"kind":"agent","id":"agent:google-gemini/agy-cross-vendor-review-2849"},"affected_crates":["aprender-gpu"],"verdict":"DEGRADED","degraded_reason":"mutation unreachable: cargo mutants tested 0 mutants because `cargo test` fails in the UNMUTATED tree (pre-existing doctest at crates/aprender-gpu/src/lib.rs:12, reproduced on the merge-base).","orchestration_disclosure":"The session that ran this skill is the SAME session that authored the diff, so it had the author's reasoning trace; this is why attestation_level is L1-self. Independence rests solely on reviewer_actor (agy, Gemini-pinned), which reviewed the diff with no access to that trace and returned agreed_with_author=false.","consultations":{"pmat":{"status":"consulted","transport":"cli","transport_unavailable":["mcp: not probed this run"],"index_commit":"9aa58d3671fe084cb9e92f219b8e2590b810b89a","index_is_ancestor":true,"complexity_delta":[],"tdg_delta":[],"satd_introduced":[],"duplication_hits":[],"cache_hits":0,"duplication_coverage":{"rust":"semantic","shell":"lexical","python":"lexical","config":"lexical","docs":"lexical","other":"lexical","sibling_branches":"none","merge_base_to_main":"none"},"duplication_horizon":["head=9aa58d3671fe084cb9e92f219b8e2590b810b89a","siblings=none (not scanned this run)","merge_base_to_main=none (not scanned this run)"],"horizon_branches_total":0,"horizon_branches_scanned":0,"merge_base_to_main_files":0,"symbols_searched":1},"cuda":{"status":"consulted","trigger_reason":"5 changed paths matched crates/aprender-gpu/** via --match-path","queries":["removing an optional GPU backend feature flag from a build while keeping other backends; conditional compilation of device backends","Metal Shading Language kernels compared to PTX; does removing a Metal backend affect CUDA PTX kernel dispatch or device enumeration"]},"crux":{"status":"not-triggered","trigger_reason":"no CLI subcommand/flag, HTTP route, MCP tool, config key or output format changed; the removed `metal` Cargo feature is a build-time surface, recorded as a semver finding under antigravity instead","surfaces":[],"contracts":[],"gap_effect":"none","crux_coverage":"covered","comparative_claims":[]},"mutation":{"status":"unreachable","scope":"in-diff","attempted":0,"killed":0,"survivors":[]},"antigravity":{"status":"consulted","attempted":1,"agy_version":"1.1.24","binary_path":"/home/noah/.local/bin/agy","model_id":"gemini-3.1-pro-high","model_family":"google/gemini","exit_code":0,"duration_seconds":86.458702413,"agy_status":"SUCCESS","usage":{"input_tokens":28293,"output_tokens":10319,"total_tokens":38612},"output_check":{"structured_output_present":true,"reviewed":true,"schema_valid":true},"reverified_by_primary":true,"divergence":{"agreed":2,"agy_only":1,"primary_only":0,"contradicted":0},"findings":[{"title":"SemVer Violation: Removal of public feature and exports","severity":"high","file":"crates/aprender-gpu/Cargo.toml","precision_class":"advisory","confidence":"high"},{"title":"Process Violation: Loss of PMAT-006 specification traceability","severity":"medium","file":"crates/aprender-gpu/tests/metal_backend_f101.rs","precision_class":"advisory","confidence":"high"},{"title":"Binary bloat on non-Apple platforms from unconditional shaders","severity":"low","file":"crates/aprender-gpu/src/backend/mod.rs","precision_class":"advisory","confidence":"high"}]}},"findings_ref":{"path":"findings.sarif","sha256":"a79d7fbba96c5ad0a546ebf9ecdf80a8e3f5f1203f6c4b1c4d5782f3440fac38"},"cost":{"input_tokens":28293,"output_tokens":10319,"wall_seconds":86}}} From 9563f3f9b9af8adf6990050d3d0d6f14042a17b1 Mon Sep 17 00:00:00 2001 From: aprender-pr-review-signer Date: Wed, 2 Sep 2026 23:01:24 +0000 Subject: [PATCH 3/3] =?UTF-8?q?chore(pr-review):=20sign=20this=20PR's=20re?= =?UTF-8?q?ceipt=20(PR-REVIEW-SKILL-002=20v2=20=C2=A74.3=20CI=20signer)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../receipt.intoto.jsonl.minisig | 4 ++++ 1 file changed, 4 insertions(+) create mode 100644 evidence/pr-review/2849/9aa58d3671fe084cb9e92f219b8e2590b810b89a/receipt.intoto.jsonl.minisig diff --git a/evidence/pr-review/2849/9aa58d3671fe084cb9e92f219b8e2590b810b89a/receipt.intoto.jsonl.minisig b/evidence/pr-review/2849/9aa58d3671fe084cb9e92f219b8e2590b810b89a/receipt.intoto.jsonl.minisig new file mode 100644 index 0000000000..8f67b03a63 --- /dev/null +++ b/evidence/pr-review/2849/9aa58d3671fe084cb9e92f219b8e2590b810b89a/receipt.intoto.jsonl.minisig @@ -0,0 +1,4 @@ +untrusted comment: signed by the CI signer +RUTeGb4p8Ma1IismhV/xZtReis67INQXM1q80OVl7S5pSrNsnGtezsT5pu088dcqxDkZtOw0dV279eo2YDSNfXC6/TIlVtjXoQQ= +trusted comment: PR-REVIEW-SKILL-002 v2 §4.3 receipt +1MF3GQp1U0JmD3TYlQZGLbLyT693YbWmMAlGE+FjJyO59TMJkeD1W5NBviZVWsI9ESnoJQ3wq5cx8brPEKemAw==