From ed21b3be800cac57b348acd3d63d546f8516a884 Mon Sep 17 00:00:00 2001 From: meh Date: Wed, 9 Sep 2026 17:37:44 +0700 Subject: [PATCH 1/2] fix(build): do not build the browser chrome for a headless binary `build_frontend` ran unconditionally, so `cargo build --bin chuzz-headless --no-default-features` shelled out to `bun run build` in `apps/chuzz/frontend` for assets that binary never links. `frontend.rs` is the only consumer of the generated module and is already behind `gui`. On a machine without `apps/chuzz/frontend/node_modules` the build script panicked instead: error: script "layouts:local" exited with code 127 error: script "prebuild" exited with code 127 That is every runner using the `headless-host` action, which installs the site under test's dependencies and has no reason to install this crate's, so the whole fleet's QA went red in the host build step. Verified both ways with `node_modules` moved aside: the headless build now finishes, and `--bin chuzz-gui` still fails there with the error above, which is the proof the gate did it rather than something else. The helpers move behind the same feature so an unused import does not become a denied warning. --- apps/chuzz/build.rs | 24 +++++++++++++++++++++++- 1 file changed, 23 insertions(+), 1 deletion(-) diff --git a/apps/chuzz/build.rs b/apps/chuzz/build.rs index 03f37e1..e5940b4 100644 --- a/apps/chuzz/build.rs +++ b/apps/chuzz/build.rs @@ -1,11 +1,21 @@ +use std::process::Command; + +// Everything below is the embedded browser chrome, which only a `gui` build +// compiles. Off that feature these are dead, and an unused import is a warning +// the workspace denies. +#[cfg(feature = "gui")] use std::fs; +#[cfg(feature = "gui")] use std::io::Write; +#[cfg(feature = "gui")] use std::path::{Path, PathBuf}; -use std::process::Command; +#[cfg(feature = "gui")] use brotli::CompressorWriter; +#[cfg(feature = "gui")] const CSS_MARKER: &str = "__CHUZZ_EMBEDDED_CSS__"; +#[cfg(feature = "gui")] const JS_URL: &str = "chuzz://ui/__chuzz__/app.js"; /// First line of a command's stdout, or `None` when it fails or prints nothing. @@ -51,6 +61,7 @@ fn stamp_build() { println!("cargo:rerun-if-changed=src"); } +#[cfg(feature = "gui")] fn only_file_with_extension(directory: &Path, extension: &str) -> PathBuf { let mut matches = fs::read_dir(directory) .unwrap_or_else(|error| panic!("cannot read {}: {error}", directory.display())) @@ -68,6 +79,7 @@ fn only_file_with_extension(directory: &Path, extension: &str) -> PathBuf { path } +#[cfg(feature = "gui")] fn compress_asset(path: &Path, output: &Path, quality: u32) -> usize { let input = fs::read(path).unwrap_or_else(|error| panic!("cannot read {}: {error}", path.display())); @@ -84,6 +96,7 @@ fn compress_asset(path: &Path, output: &Path, quality: u32) -> usize { /// Compile and Brotli-embed the Solid browser chrome using the same asset /// loading shape as AgencyZero's Blitz document factory. +#[cfg(feature = "gui")] fn build_frontend() { let manifest_dir = PathBuf::from( std::env::var_os("CARGO_MANIFEST_DIR").expect("Cargo sets CARGO_MANIFEST_DIR"), @@ -170,6 +183,15 @@ fn strip_unused_frameworks() { fn main() { strip_unused_frameworks(); stamp_build(); + // The Solid browser chrome, consumed only by `frontend.rs`, which is itself + // behind `gui`. Building it unconditionally meant `cargo build --bin + // chuzz-headless --no-default-features` shelled out to `bun run build` for + // assets that binary never links, and then failed on any machine where + // `apps/chuzz/frontend/node_modules` was not installed. That is every CI + // runner using the headless-host action, which installs the site's + // dependencies and has no reason to install this crate's. It took the whole + // fleet's QA red. + #[cfg(feature = "gui")] build_frontend(); // Generates the Tauri context, which only the `chuzz-gui` binary consumes. // A headless build has no `tauri` in its graph for the context to describe, From 68157ee3efc09ee982364b4a32a954f44e63eea8 Mon Sep 17 00:00:00 2001 From: meh Date: Wed, 9 Sep 2026 17:37:54 +0700 Subject: [PATCH 2/2] fix(tls): name the crypto provider instead of implying it `rustls` picks its provider from crate features and panics at the first handshake when the graph enables neither `ring` nor `aws-lc-rs`, or both. Features are additive across a graph, so which of those holds is an outcome of resolution rather than a decision anyone made, and with no lockfile it is not fixed at any point in time: one dependency picking up `ring` in a later release is enough to turn every `https://` fetch and every `wss://` connection into a panicked worker on the next runner that resolves it. It was hit on a fresh resolution during QA work, on `rustls 0.23.43`, and went away on re-resolution to 0.23.44. Today's graph enables `aws-lc-rs` alone, so this is latent rather than reproducible here, which is exactly the problem: nothing holds it there. The failure does not look like a browser failure. The socket never opens, Solid halts reactivity on the escaped error, and the page collapses to unnamed nodes, so a QA run reports a broken site. Both binaries now install a named provider before anything can reach the network. Verified by capturing an https page end to end. --- Cargo.toml | 8 ++++++++ apps/chuzz/Cargo.toml | 1 + apps/chuzz/src/headless_main.rs | 4 ++++ apps/chuzz/src/lib.rs | 22 ++++++++++++++++++++++ apps/chuzz/src/tauri_main.rs | 5 +++++ 5 files changed, 40 insertions(+) diff --git a/Cargo.toml b/Cargo.toml index 87791f8..a194a13 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -98,6 +98,14 @@ tokio-tungstenite = { version = "0.30", default-features = false, features = [ "handshake", "rustls-tls-native-roots", ] } +# Named directly only so the binary can install a crypto provider by name. See +# `install_crypto_provider`: rustls otherwise picks one from crate features and +# panics at the first handshake when the resolution enables neither provider or +# both, and with no lockfile that is not fixed at any point in time. +rustls = { version = "0.23", default-features = false, features = [ + "aws-lc-rs", + "std", +] } futures-util = { version = "0.3", default-features = false, features = ["sink"] } tokio = "1" url = "2.5" diff --git a/apps/chuzz/Cargo.toml b/apps/chuzz/Cargo.toml index 790f0bc..9147919 100644 --- a/apps/chuzz/Cargo.toml +++ b/apps/chuzz/Cargo.toml @@ -151,6 +151,7 @@ url.workspace = true serde.workspace = true serde_json.workspace = true tauri = { workspace = true, optional = true } +rustls.workspace = true tauri-runtime-blitz.workspace = true tokio-tungstenite.workspace = true futures-util.workspace = true diff --git a/apps/chuzz/src/headless_main.rs b/apps/chuzz/src/headless_main.rs index bf50d0d..0ab4c17 100644 --- a/apps/chuzz/src/headless_main.rs +++ b/apps/chuzz/src/headless_main.rs @@ -14,6 +14,10 @@ use chuzz_gui::serve; fn main() { + // Before the loader can reach the network. See the function's own comment + // for why the provider is named here rather than left to the resolution. + chuzz_gui::install_crypto_provider(); + let args: Vec = std::env::args().collect(); let target = match serve::target_from(&args) { Ok(target) => target, diff --git a/apps/chuzz/src/lib.rs b/apps/chuzz/src/lib.rs index fa63d6e..7ce675c 100644 --- a/apps/chuzz/src/lib.rs +++ b/apps/chuzz/src/lib.rs @@ -14,6 +14,28 @@ //! the harness, so the harness measured a browser nobody ships. There is one //! loader now, and one place a gap gets fixed. +/// Name the TLS provider, rather than letting the resolver imply one. +/// +/// `rustls` selects its cryptographic provider from crate features, and panics +/// at the first handshake when the graph enables neither `ring` nor `aws-lc-rs` +/// or enables both. Features are additive across a dependency graph, so which +/// of those holds is an outcome of resolution rather than a decision anyone +/// made. This repository commits no lockfile, so it is not fixed at any point +/// in time either: one crate picking up `ring` in a later release is enough to +/// turn every `https://` fetch and every `wss://` connection into a panicked +/// worker on the next runner that resolves it. +/// +/// It presents as a site bug rather than a browser one. The socket never +/// opens, Solid halts reactivity on the escaped error, and the page collapses +/// to unnamed nodes, so a QA run reports a broken site. +/// +/// Both binaries call this before anything can reach the network. An `Err` +/// means a provider is already installed, which is the outcome being asked +/// for, so it is discarded. +pub fn install_crypto_provider() { + let _ = rustls::crypto::aws_lc_rs::default_provider().install_default(); +} + // The window and its Tauri command surface. Behind `gui` because `tauri` is, // and because a headless build has no window to drive. #[cfg(feature = "gui")] diff --git a/apps/chuzz/src/tauri_main.rs b/apps/chuzz/src/tauri_main.rs index 21e2c6c..c9056ec 100644 --- a/apps/chuzz/src/tauri_main.rs +++ b/apps/chuzz/src/tauri_main.rs @@ -114,6 +114,11 @@ fn flag_value(args: &[String], flag: &str) -> Option { } fn main() { + // Before any of the entry points below can reach the network. See the + // function's own comment for why the provider is named here rather than + // left to the resolution. + chuzz_gui::install_crypto_provider(); + // Before `--capture`, and matched by equality rather than by prefix, so the // two flags cannot be confused for each other in either direction. #[cfg(feature = "capture")]