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/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, 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")]