ci: add wasm32 to the toolchain and gate the bundle crates (improvise-ywd)
The audit could not start because the Nix toolchain shipped no wasm32 std and there is no rustup here. Adding targets = ["wasm32-unknown-unknown"] to the flake's rustToolchain was the whole blocker: all four bundle crates — improvise-core, improvise-formula, improvise-io, improvise-command — then check clean on the first try with no feature flags and no source changes. The root improvise crate does not build for wasm because crossterm does not. That is correct: it is the terminal binary, not part of the browser bundle, so the CI job names the four bundle crates rather than checking the workspace. Adds .github/workflows/ci.yml with that wasm job plus a host job running fmt, clippy -D warnings and the test suite — there was no test or lint CI before, only cargo-dist's release workflow. A check gate cannot catch what merely panics at runtime, and the audit's real finding was that two of the ticket's six named hazards are real while four are not. Instant::now() panics on wasm yet compiles, so improvise-command now uses web-time, which re-exports std::time off-wasm. std::fs likewise compiles and then fails in a browser; StartImportWizard and LoadModel still bypass the Storage trait, filed as improvise-fuj because the fix needs a decision on how a wasm build injects its backend. Verified by compilation on both targets and by the native suite. Nothing here has been run on a wasm host. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01V69PGQfMJuGRmtSWpfVAqn
This commit is contained in:
@@ -0,0 +1,43 @@
|
||||
name: CI
|
||||
|
||||
on:
|
||||
push:
|
||||
pull_request:
|
||||
|
||||
env:
|
||||
CARGO_TERM_COLOR: always
|
||||
|
||||
jobs:
|
||||
test:
|
||||
name: Test and lint (host)
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: dtolnay/rust-toolchain@stable
|
||||
with:
|
||||
components: clippy, rustfmt
|
||||
- uses: Swatinem/rust-cache@v2
|
||||
- run: cargo fmt --all -- --check
|
||||
- run: cargo clippy --workspace --all-targets -- -D warnings
|
||||
- run: cargo test --workspace
|
||||
|
||||
wasm:
|
||||
name: wasm32 check (browser bundle crates)
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: dtolnay/rust-toolchain@stable
|
||||
with:
|
||||
targets: wasm32-unknown-unknown
|
||||
- uses: Swatinem/rust-cache@v2
|
||||
# Only the crates that go into the browser bundle. The root `improvise`
|
||||
# crate is the terminal binary and depends on crossterm, which does not
|
||||
# build for wasm — that is correct, not a gap (improvise-ywd).
|
||||
- name: improvise-core
|
||||
run: cargo check -p improvise-core --target wasm32-unknown-unknown
|
||||
- name: improvise-formula
|
||||
run: cargo check -p improvise-formula --target wasm32-unknown-unknown
|
||||
- name: improvise-io
|
||||
run: cargo check -p improvise-io --target wasm32-unknown-unknown
|
||||
- name: improvise-command
|
||||
run: cargo check -p improvise-command --target wasm32-unknown-unknown
|
||||
Generated
+11
@@ -798,6 +798,7 @@ dependencies = [
|
||||
"serde_json",
|
||||
"tempfile",
|
||||
"unicode-width",
|
||||
"web-time",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -2115,6 +2116,16 @@ dependencies = [
|
||||
"unicode-ident",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "web-time"
|
||||
version = "1.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5a6580f308b1fad9207618087a65c04e7a10bc77e02c8e84e9b00dd4b12fa0bb"
|
||||
dependencies = [
|
||||
"js-sys",
|
||||
"wasm-bindgen",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wezterm-bidi"
|
||||
version = "0.2.3"
|
||||
|
||||
@@ -249,6 +249,20 @@ docs/design-notes.md Product vision & non-goals
|
||||
|
||||
---
|
||||
|
||||
## Wasm
|
||||
|
||||
The browser bundle is `improvise-core`, `improvise-formula`, `improvise-io`
|
||||
and `improvise-command`. All four `cargo check --target
|
||||
wasm32-unknown-unknown` clean with no feature flags (improvise-ywd), gated by
|
||||
the `wasm` job in `.github/workflows/ci.yml`. The root `improvise` crate is
|
||||
the terminal binary and does **not** build for wasm — it depends on
|
||||
crossterm; that is correct, not a gap.
|
||||
|
||||
`cargo check` passing is not the same as running. Two known runtime hazards
|
||||
it cannot see: `Instant::now()` panics on wasm (hence `web-time` in
|
||||
`improvise-command`), and any `std::fs` call compiles but fails in a browser
|
||||
— see improvise-fuj for the two effects that still bypass `Storage`.
|
||||
|
||||
## CLI
|
||||
|
||||
```
|
||||
|
||||
@@ -13,6 +13,9 @@ improvise-io = { path = "../improvise-io" }
|
||||
anyhow = "1"
|
||||
serde_json = "1"
|
||||
unicode-width = "^0.2"
|
||||
# Instant::now() panics on wasm32-unknown-unknown; web_time re-exports
|
||||
# std::time off-wasm and uses performance.now() on it (improvise-ywd).
|
||||
web-time = "1"
|
||||
|
||||
[dev-dependencies]
|
||||
tempfile = "3"
|
||||
|
||||
@@ -3,7 +3,10 @@ use anyhow::Result;
|
||||
use std::collections::HashMap;
|
||||
use std::path::PathBuf;
|
||||
use std::sync::Arc;
|
||||
use std::time::{Duration, Instant};
|
||||
use std::time::Duration;
|
||||
// Not std::time::Instant: that panics on wasm32-unknown-unknown, and this
|
||||
// crate is part of the browser bundle (improvise-ywd).
|
||||
use web_time::Instant;
|
||||
|
||||
use std::rc::Rc;
|
||||
|
||||
|
||||
@@ -25,6 +25,10 @@
|
||||
|
||||
rustToolchain = pkgs.rust-bin.stable.latest.default.override {
|
||||
extensions = ["rust-src" "clippy" "rustfmt" "llvm-tools-preview"];
|
||||
# The browser deployment targets wasm; without this, the wasm audit
|
||||
# (improvise-ywd) and the OPFS backend (improvise-i34) cannot even be
|
||||
# checked, let alone fixed.
|
||||
targets = ["wasm32-unknown-unknown"];
|
||||
};
|
||||
|
||||
cargoNix = import ./Cargo.nix {
|
||||
|
||||
@@ -188,3 +188,21 @@ invariant(prefix_is_an_address,'ViewState::transient_prefix stores the (KeyCode,
|
||||
decision('improvise-jc8',flip_the_arc,'Arc<Keymap> moved out of ViewState and back into KeymapSet, which already owns every keymap. Motivated by a hard dependency cycle -- Keymap is in improvise-command, which will depend on improvise-protocol once reduce_full lives there -- not by serde.').
|
||||
work_done('improvise-jc8',refactor,'ViewState no longer holds Arc<Keymap>; four characterization tests written before the change pinned gg/ZZ/prefix-consumption/records-inheritance, plus two new guards for flatness and parent resolution.').
|
||||
location(prefix_resolution,'crates/improvise-command/src/command/keymap.rs','KeymapSet::prefix_keymap, SetTransientPrefix').
|
||||
|
||||
% ── Wasm compatibility (improvise-ywd, audited 2026-08-30) ──────────────────
|
||||
wasm_bundle_crate('improvise-core').
|
||||
wasm_bundle_crate('improvise-formula').
|
||||
wasm_bundle_crate('improvise-io').
|
||||
wasm_bundle_crate('improvise-command').
|
||||
not_in_wasm_bundle('improvise', 'terminal binary; depends on crossterm').
|
||||
wasm_checks_clean(all_bundle_crates, no_feature_flags_needed).
|
||||
% Hazards a compile check cannot see.
|
||||
wasm_runtime_hazard(instant_now, 'panics on wasm32-unknown-unknown', fixed_with(web_time)).
|
||||
wasm_runtime_hazard(std_fs, 'compiles on wasm but fails in a browser', tracked_as('improvise-fuj')).
|
||||
wasm_non_hazard(dirs, 'declared in root Cargo.toml but used nowhere').
|
||||
wasm_non_hazard(chrono_local_now, 'never called').
|
||||
wasm_non_hazard(std_env, 'only in a test').
|
||||
wasm_non_hazard(flate2, 'compiles for wasm as-is').
|
||||
invariant(compile_check_is_not_runtime_proof,'the wasm CI gate is cargo check, so it catches nothing that merely panics at runtime: Instant and std::fs both COMPILE for wasm32-unknown-unknown').
|
||||
work_done('improvise-ywd',audit,'added wasm32-unknown-unknown to the flake toolchain, which was the real blocker; all four bundle crates then checked clean with zero source changes. Swapped Instant for web_time and added the CI wasm job.').
|
||||
location(wasm_ci,'.github/workflows/ci.yml','wasm job').
|
||||
|
||||
Reference in New Issue
Block a user