From 391e310e9ff17d38c693731eb12fcce8e0fc47c1 Mon Sep 17 00:00:00 2001 From: George Oastler Date: Sat, 24 Oct 2026 01:24:17 +0100 Subject: [PATCH 1/6] Build the Rust addons in debug outside production build now runs build:debug unless NODE_ENV=production, where it runs build:release with the existing full-LTO profile. Dependencies are optimised in debug (opt-level 2) so tests stay fast. CI cargo test drops --release and gains a cargo cache keyed on the exact rustc. --- .../native-debug-builds-outside-release.md | 7 ++++++ .github/workflows/tests.yml | 24 ++++++++++++++++++- packages/native-ja4/Cargo.toml | 5 ++++ packages/native-ja4/package.json | 5 ++-- packages/native-merkle/Cargo.toml | 5 ++++ packages/native-merkle/package.json | 5 ++-- packages/native-puzzle/Cargo.toml | 4 ++++ packages/native-puzzle/package.json | 7 +++--- 8 files changed, 54 insertions(+), 8 deletions(-) create mode 100644 .changeset/native-debug-builds-outside-release.md diff --git a/.changeset/native-debug-builds-outside-release.md b/.changeset/native-debug-builds-outside-release.md new file mode 100644 index 0000000000..b78331575e --- /dev/null +++ b/.changeset/native-debug-builds-outside-release.md @@ -0,0 +1,7 @@ +--- +"@prosopo/native-ja4": patch +"@prosopo/native-merkle": patch +"@prosopo/native-puzzle": patch +--- + +The Rust addons now build in debug mode unless `NODE_ENV=production`, so local builds and CI tests stop paying for full link-time optimisation. Production builds (npm publish, the provider image) still run `build:release` with full LTO. Dependencies are optimised even in debug so tests stay fast, and the CI `cargo test` job now runs in debug with a cargo cache instead of a cold release build. diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 0129066d9b..be840b8c50 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -209,6 +209,28 @@ jobs: with: submodules: false + - name: Record the rustc version for the cache key + id: rustc-version + run: | + release=$(rustc -vV | sed -n 's/^release: //p') + commit=$(rustc -vV | sed -n 's/^commit-hash: //p') + if [ -z "$release" ]; then + echo "::error::could not read release from rustc -vV"; exit 1 + fi + echo "version=${release}${commit:+-${commit:0:9}}" >> "$GITHUB_OUTPUT" + + # The compiler goes in prefix-key, not just key: rust-cache derives its + # restore-keys from the prefix, so a version left outside it can restore a + # target/ built by another rustc, which cargo rejects with E0514. + - name: Cache cargo + uses: Swatinem/rust-cache@6323deb102c322ba6fcbdcafc7e3dddab59af2b6 # v2 + with: + workspaces: ${{ matrix.crate }} + prefix-key: v0-rust-${{ steps.rustc-version.outputs.version }} + + # Debug, not --release: the release profile's fat LTO and single codegen + # unit are for the shipped addon, and only slow a test build down. The + # dependencies are still optimised via profile.dev.package."*". - name: cargo test working-directory: ${{ matrix.crate }} - run: cargo test --release --locked + run: cargo test --locked diff --git a/packages/native-ja4/Cargo.toml b/packages/native-ja4/Cargo.toml index 65dcb2b088..c73c4c6921 100644 --- a/packages/native-ja4/Cargo.toml +++ b/packages/native-ja4/Cargo.toml @@ -26,5 +26,10 @@ strip = "symbols" # Debug symbols for our own crate only. Dependency debuginfo is the bulk of a # dev build and nobody steps through third-party code here; CI narrows our own # to line tables via CARGO_PROFILE_DEV_DEBUG. +# +# Dependencies optimised even in debug: they compile once and are cached, so +# tests run near release speed while our own crate still rebuilds quickly. +# Releases keep full LTO via `build:release` (`NODE_ENV=production pnpm build`). [profile.dev.package."*"] debug = false +opt-level = 2 diff --git a/packages/native-ja4/package.json b/packages/native-ja4/package.json index e76dd56177..7889d15aa7 100644 --- a/packages/native-ja4/package.json +++ b/packages/native-ja4/package.json @@ -20,10 +20,11 @@ "targets": ["x86_64-unknown-linux-gnu"] }, "scripts": { - "build": "napi build --platform --release", + "build": "if [ \"$NODE_ENV\" = production ]; then pnpm run build:release; else pnpm run build:debug; fi", "build:debug": "napi build --platform", + "build:release": "napi build --platform --release", "test": "node --test \"tests/**/*.test.ts\"", - "test:cargo": "cargo test --release", + "test:cargo": "cargo test", "clean": "cargo clean && rm -f *.node index.js index.d.ts" }, "devDependencies": { diff --git a/packages/native-merkle/Cargo.toml b/packages/native-merkle/Cargo.toml index 720ca0b00b..9d3a64a951 100644 --- a/packages/native-merkle/Cargo.toml +++ b/packages/native-merkle/Cargo.toml @@ -30,5 +30,10 @@ strip = "symbols" # Debug symbols for our own crate only. Dependency debuginfo is the bulk of a # dev build and nobody steps through third-party code here; CI narrows our own # to line tables via CARGO_PROFILE_DEV_DEBUG. +# +# Dependencies optimised even in debug: they compile once and are cached, so +# tests run near release speed while our own crate still rebuilds quickly. +# Releases keep full LTO via `build:release` (`NODE_ENV=production pnpm build`). [profile.dev.package."*"] debug = false +opt-level = 2 diff --git a/packages/native-merkle/package.json b/packages/native-merkle/package.json index 46b4262168..91f8a0196c 100644 --- a/packages/native-merkle/package.json +++ b/packages/native-merkle/package.json @@ -20,9 +20,10 @@ "targets": ["x86_64-unknown-linux-gnu"] }, "scripts": { - "build": "napi build --platform --release", + "build": "if [ \"$NODE_ENV\" = production ]; then pnpm run build:release; else pnpm run build:debug; fi", "build:debug": "napi build --platform", - "test:cargo": "cargo test --release", + "build:release": "napi build --platform --release", + "test:cargo": "cargo test", "clean": "cargo clean && rm -f *.node index.js index.d.ts" }, "devDependencies": { diff --git a/packages/native-puzzle/Cargo.toml b/packages/native-puzzle/Cargo.toml index e1001883d6..9cfe613ce7 100644 --- a/packages/native-puzzle/Cargo.toml +++ b/packages/native-puzzle/Cargo.toml @@ -20,5 +20,9 @@ lto = true codegen-units = 1 strip = "symbols" +# Dependencies optimised even in debug: they compile once and are cached, so +# tests run near release speed while our own crate still rebuilds quickly. +# Releases keep full LTO via `build:release` (`NODE_ENV=production pnpm build`). [profile.dev.package."*"] debug = false +opt-level = 2 diff --git a/packages/native-puzzle/package.json b/packages/native-puzzle/package.json index b8ee52698e..8ec1165c3c 100644 --- a/packages/native-puzzle/package.json +++ b/packages/native-puzzle/package.json @@ -20,10 +20,11 @@ "targets": ["x86_64-unknown-linux-gnu"] }, "scripts": { - "build": "napi build --platform --release", + "build": "if [ \"$NODE_ENV\" = production ]; then pnpm run build:release; else pnpm run build:debug; fi", "build:debug": "napi build --platform", - "bench": "node bench.mjs", - "test:cargo": "cargo test --release", + "build:release": "napi build --platform --release", + "bench": "pnpm run build:release && node bench.mjs", + "test:cargo": "cargo test", "test": "node --test tests/dimensions.test.ts", "clean": "cargo clean && rm -f *.node" }, From 3d46ba2357063aa9c00cdaaffa652f47859f4ee2 Mon Sep 17 00:00:00 2001 From: George Oastler Date: Sat, 24 Oct 2026 01:31:27 +0100 Subject: [PATCH 2/6] Keep the PAT away from the cargo-test job --- .github/workflows/tests.yml | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index be840b8c50..0f6753f4d8 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -197,6 +197,11 @@ jobs: name: cargo test (${{ matrix.crate }}) runs-on: ubuntu-latest if: github.event.pull_request.draft == false + # Blank the workflow-level PAT: nothing here needs it, and a third-party + # action (rust-cache) runs in this job. + env: + GITHUB_TOKEN: "" + GH_TOKEN: "" strategy: fail-fast: false matrix: From 7c412bcd0d4ff11e22cdf4679dcf4b9757aab500 Mon Sep 17 00:00:00 2001 From: George Oastler Date: Sat, 24 Oct 2026 01:32:15 +0100 Subject: [PATCH 3/6] Drop the cargo cache from cargo-test --- .github/workflows/tests.yml | 19 ------------------- 1 file changed, 19 deletions(-) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 0f6753f4d8..45dd224383 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -214,25 +214,6 @@ jobs: with: submodules: false - - name: Record the rustc version for the cache key - id: rustc-version - run: | - release=$(rustc -vV | sed -n 's/^release: //p') - commit=$(rustc -vV | sed -n 's/^commit-hash: //p') - if [ -z "$release" ]; then - echo "::error::could not read release from rustc -vV"; exit 1 - fi - echo "version=${release}${commit:+-${commit:0:9}}" >> "$GITHUB_OUTPUT" - - # The compiler goes in prefix-key, not just key: rust-cache derives its - # restore-keys from the prefix, so a version left outside it can restore a - # target/ built by another rustc, which cargo rejects with E0514. - - name: Cache cargo - uses: Swatinem/rust-cache@6323deb102c322ba6fcbdcafc7e3dddab59af2b6 # v2 - with: - workspaces: ${{ matrix.crate }} - prefix-key: v0-rust-${{ steps.rustc-version.outputs.version }} - # Debug, not --release: the release profile's fat LTO and single codegen # unit are for the shipped addon, and only slow a test build down. The # dependencies are still optimised via profile.dev.package."*". From 11372b8f5d63d1a1f12c0f15150e0029b2d4736d Mon Sep 17 00:00:00 2001 From: George Oastler Date: Sat, 24 Oct 2026 01:33:04 +0100 Subject: [PATCH 4/6] Restore tests.yml to main --- .github/workflows/tests.yml | 10 +--------- 1 file changed, 1 insertion(+), 9 deletions(-) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 45dd224383..0129066d9b 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -197,11 +197,6 @@ jobs: name: cargo test (${{ matrix.crate }}) runs-on: ubuntu-latest if: github.event.pull_request.draft == false - # Blank the workflow-level PAT: nothing here needs it, and a third-party - # action (rust-cache) runs in this job. - env: - GITHUB_TOKEN: "" - GH_TOKEN: "" strategy: fail-fast: false matrix: @@ -214,9 +209,6 @@ jobs: with: submodules: false - # Debug, not --release: the release profile's fat LTO and single codegen - # unit are for the shipped addon, and only slow a test build down. The - # dependencies are still optimised via profile.dev.package."*". - name: cargo test working-directory: ${{ matrix.crate }} - run: cargo test --locked + run: cargo test --release --locked From 16b2a30116daa691b3c16853d23aaa828bfc6674 Mon Sep 17 00:00:00 2001 From: George Oastler Date: Sat, 24 Oct 2026 01:33:33 +0100 Subject: [PATCH 5/6] Run cargo-test in the debug profile --- .github/workflows/tests.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 0129066d9b..d5c3dd9537 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -211,4 +211,4 @@ jobs: - name: cargo test working-directory: ${{ matrix.crate }} - run: cargo test --release --locked + run: cargo test --locked From 4f60eb6ba3312bedc893d670a15eb6ab6cd438fd Mon Sep 17 00:00:00 2001 From: George Oastler Date: Sat, 24 Oct 2026 01:34:17 +0100 Subject: [PATCH 6/6] Move cargo-test into its own workflow --- .github/workflows/cargo-test.yml | 82 ++++++++++++++++++++++++++++++++ .github/workflows/tests.yml | 27 +---------- 2 files changed, 84 insertions(+), 25 deletions(-) create mode 100644 .github/workflows/cargo-test.yml diff --git a/.github/workflows/cargo-test.yml b/.github/workflows/cargo-test.yml new file mode 100644 index 0000000000..54b4f80752 --- /dev/null +++ b/.github/workflows/cargo-test.yml @@ -0,0 +1,82 @@ +# The napi crates carry Rust unit tests that `pnpm run test:all` never runs: +# turbo only drives the JS suites. Its own workflow, not a tests.yml job, so a +# Rust failure is reported on its own and does not wait for the full JS build, +# and so it runs only when a crate can have changed. + +name: cargo-test + +on: + pull_request: + branches: [main, dev, staging, release/*] + # Safe against the "required check never reports" trap: required-check.yml + # publishes `check` with no path filter and waits on every check run, so a + # PR touching neither path still gets a `check`. + paths: + - 'packages/native-*/**' + - '.github/workflows/cargo-test.yml' + types: + - opened + - synchronize + - reopened + - ready_for_review + workflow_dispatch: + +permissions: + contents: read + +concurrency: + group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }} + cancel-in-progress: true + +env: + CARGO_TERM_COLOR: always + CARGO_INCREMENTAL: "0" + CARGO_PROFILE_DEV_DEBUG: line-tables-only + +defaults: + run: + shell: bash + +jobs: + cargo-test: + name: cargo test (${{ matrix.crate }}) + # Hosted for the same reason as tests.yml: this repository is public. + runs-on: ubuntu-latest + if: github.event.pull_request.draft == false + strategy: + fail-fast: false + matrix: + # Independent crates, each with its own lockfile. A new + # packages/native-* crate is not tested until it is listed here. + crate: + [packages/native-ja4, packages/native-merkle, packages/native-puzzle] + steps: + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + with: + submodules: false + + - name: Record the rustc version for the cache key + id: rustc-version + run: | + release=$(rustc -vV | sed -n 's/^release: //p') + commit=$(rustc -vV | sed -n 's/^commit-hash: //p') + if [ -z "$release" ]; then + echo "::error::could not read release from rustc -vV"; exit 1 + fi + echo "version=${release}${commit:+-${commit:0:9}}" >> "$GITHUB_OUTPUT" + + # The compiler goes in prefix-key, not just key: rust-cache derives its + # restore-keys from the prefix, so a version left outside it can restore a + # target/ built by another rustc, which cargo rejects with E0514. + - name: Cache cargo + uses: Swatinem/rust-cache@6323deb102c322ba6fcbdcafc7e3dddab59af2b6 # v2 + with: + workspaces: ${{ matrix.crate }} + prefix-key: v0-rust-${{ steps.rustc-version.outputs.version }} + + # Debug, not --release: the release profile's fat LTO and single codegen + # unit are for the shipped addon, and only slow a test build down. The + # dependencies are still optimised via profile.dev.package."*". + - name: cargo test + working-directory: ${{ matrix.crate }} + run: cargo test --locked diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index d5c3dd9537..aefcfcd0ad 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -162,8 +162,8 @@ jobs: # way CSS allows, and the ones that size it by *measuring* it have broken # repeatedly and silently: the widget renders, just at its minimum width. # - # Kept out of `check` for the same reason as cargo-test, and because it needs - # none of what that job builds -- no docker, no provider, no workspace build, + # Kept out of `check` so its failure is reported on its own, and because it + # needs none of what that job builds -- no docker, no provider, no workspace build, # since the package imports nothing from the workspace at runtime. It still # gates the merge: required-check.yml waits on every check run on the commit, # so there is no list to add this to. @@ -189,26 +189,3 @@ jobs: run: pnpm --filter @prosopo/widget-skeleton exec playwright install --with-deps chromium - run: pnpm --filter @prosopo/widget-skeleton run test:browser - - # The napi crates carry Rust unit tests that `pnpm run test:all` never runs: - # turbo only drives the JS suites. Kept out of `check` so a Rust failure is - # reported on its own and does not wait for the full JS build. - cargo-test: - name: cargo test (${{ matrix.crate }}) - runs-on: ubuntu-latest - if: github.event.pull_request.draft == false - strategy: - fail-fast: false - matrix: - # Independent crates, each with its own lockfile. A new - # packages/native-* crate is not tested until it is listed here. - crate: - [packages/native-ja4, packages/native-merkle, packages/native-puzzle] - steps: - - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 - with: - submodules: false - - - name: cargo test - working-directory: ${{ matrix.crate }} - run: cargo test --locked