From 01336a834ace81acedef3b0ee2d1f09cbbceef9f Mon Sep 17 00:00:00 2001 From: Vinnie Falco Date: Fri, 11 Sep 2026 13:49:08 -0700 Subject: [PATCH 1/3] Unify local and CI on stable Rust, drop MSRV machinery Local builds were pinned to an older Rust while CI linted and tested on stable, and that skew just produced three CI failures on a pull request that had passed every local check. This change puts local builds and CI on the same stable toolchain, removes the minimum-version declarations and the CI job that enforced them, and deletes the runner validation script together with its dedicated test. The three failures the skew exposed are fixed in place: an unnecessary-parentheses lint, a pair of lint expectations consolidated under a new name, and a paused-clock test structurally incompatible with its real-time rendezvous. - `rust-toolchain.toml` now selects `channel = "stable"`, so local builds track the same toolchain CI uses; the header comment describes that rule instead of the removed pin. - `rust-version` leaves the workspace manifest and the crate manifests, ending the resolver's preference for dependency versions compatible with the old minimum. - `crates/gateway-stt/src/audio.rs` drops the parentheses around the trait object type in an error-chain assertion, clearing the lint stable fires on them. - `crates/workshop/src/bridge.rs` expects `clippy::ref_as_ptr` on the test module in place of `clippy::ptr_as_ptr` and `clippy::borrow_as_ptr`, which stable consolidated into the single lint. - `decode_rendezvous_timeout_releases_a_late_arrival_and_permits_a_follow_up` runs on the real clock again, because a paused clock cannot drive the test's real-time condvar rendezvous. - `.github/workflows/ci.yml` loses the `msrv` job and every `RUSTUP_TOOLCHAIN` override, and `ci-green` no longer lists `msrv` among its needs. - `tools/validate-rust-1.89.0.ps1` is deleted along with `tools/check-stt-native-workflow.test.mjs`, the test file that existed only to exercise it. - `README.md` loses the minimum-version section, the version badge, and the version number in the build instructions; `AGENTS.md` loses the bullet warning that CI lints on a newer toolchain than local. Design: removes feature-flag @ .github/workflows/ci.yml Plan: vibe/2026-09-11-2-unify-toolchain.md --- .github/workflows/ci.yml | 41 +--- .github/workflows/release-workshop.yml | 5 - .github/workflows/stt-miri.yml | 7 - AGENTS.md | 1 - Cargo.toml | 1 - README.md | 7 +- crates/build-llama-cuda/Cargo.toml | 1 - crates/build-ui/Cargo.toml | 1 - crates/build-workshop/Cargo.toml | 1 - crates/gateway-config-ui/Cargo.toml | 1 - crates/gateway-config/Cargo.toml | 1 - crates/gateway-local/Cargo.toml | 1 - crates/gateway-logging/Cargo.toml | 1 - crates/gateway-routing/Cargo.toml | 1 - crates/gateway-stt-backend-whisper/Cargo.toml | 1 - crates/gateway-stt-engine/Cargo.toml | 1 - .../tests/scenario_cleanup/decode.rs | 2 +- crates/gateway-stt/Cargo.toml | 1 - crates/gateway-stt/src/audio.rs | 2 +- crates/gateway-web-search/Cargo.toml | 1 - crates/gateway-whisper-ffi/Cargo.toml | 1 - crates/gateway/Cargo.toml | 1 - crates/product-integration-tests/Cargo.toml | 1 - crates/promptforge-agent/Cargo.toml | 1 - crates/promptforge-core-support/Cargo.toml | 1 - crates/promptforge-core/Cargo.toml | 1 - crates/promptforge-lua/Cargo.toml | 1 - crates/promptforge-model-client/Cargo.toml | 1 - crates/promptforge-parser/Cargo.toml | 1 - crates/promptforge-store/Cargo.toml | 1 - crates/promptforge-tool-picker/Cargo.toml | 1 - crates/promptforge-tools/Cargo.toml | 1 - crates/promptforge-web-search/Cargo.toml | 1 - crates/promptforge-webfetch/Cargo.toml | 1 - crates/promptforge/Cargo.toml | 1 - crates/shared-loopback/Cargo.toml | 1 - crates/shared-progress/Cargo.toml | 1 - crates/shared-protocol/Cargo.toml | 1 - crates/shared-sidecar/Cargo.toml | 1 - crates/workshop-server/Cargo.toml | 1 - crates/workshop/Cargo.toml | 1 - crates/workshop/src/bridge.rs | 3 +- rust-toolchain.toml | 7 +- tools/check-stt-native-workflow.test.mjs | 223 ------------------ tools/validate-rust-1.89.0.ps1 | 66 ------ vibe-ledger.md | 2 + vibe/2026-09-11-2-unify-toolchain.md | 157 ++++++++++++ vibe/ACTIVE | 1 + 48 files changed, 167 insertions(+), 391 deletions(-) delete mode 100644 tools/check-stt-native-workflow.test.mjs delete mode 100644 tools/validate-rust-1.89.0.ps1 create mode 100644 vibe/2026-09-11-2-unify-toolchain.md create mode 100644 vibe/ACTIVE diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 648307bd0..fdcb1f567 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -16,7 +16,6 @@ concurrency: # to anonymous, which is what the cache is there to make rare. env: HF_TOKEN: ${{ secrets.HF_TOKEN }} - RUSTUP_TOOLCHAIN: stable jobs: fmt: @@ -33,8 +32,6 @@ jobs: clippy: runs-on: ubuntu-latest - env: - RUSTUP_TOOLCHAIN: stable steps: - uses: actions/checkout@v4 @@ -83,8 +80,6 @@ jobs: test: runs-on: ubuntu-latest - env: - RUSTUP_TOOLCHAIN: stable steps: - uses: actions/checkout@v4 @@ -133,7 +128,6 @@ jobs: docs: runs-on: ubuntu-latest env: - RUSTUP_TOOLCHAIN: stable RUSTDOCFLAGS: -D warnings steps: - uses: actions/checkout@v4 @@ -162,8 +156,6 @@ jobs: check-workshop: runs-on: windows-latest - env: - RUSTUP_TOOLCHAIN: stable steps: - uses: actions/checkout@v4 @@ -234,8 +226,6 @@ jobs: # missing-system-package class of failure the Windows-only job cannot see. check-workshop-linux: runs-on: ubuntu-22.04 - env: - RUSTUP_TOOLCHAIN: stable steps: - uses: actions/checkout@v4 @@ -341,35 +331,6 @@ jobs: working-directory: crates/gateway-config-ui/ui run: npm test - msrv: - runs-on: ubuntu-latest - env: - RUSTUP_TOOLCHAIN: 1.89.0 - steps: - - uses: actions/checkout@v4 - - - uses: dtolnay/rust-toolchain@1.89.0 - - - name: Cache cargo - uses: Swatinem/rust-cache@v2 - - - name: Cache the embedding model - uses: ./.github/actions/hf-model-cache - - - uses: actions/setup-node@v4 - with: - node-version: 22 - cache: npm - cache-dependency-path: crates/*/ui/package-lock.json - - - name: Install UI dependencies - run: | - npm ci --prefix crates/workshop-server/ui - npm ci --prefix crates/gateway-config-ui/ui - - - name: Check on MSRV - run: cargo check --locked --workspace --exclude workshop --exclude workshop-server --all-targets --all-features - supply-chain: runs-on: ubuntu-latest steps: @@ -391,7 +352,7 @@ jobs: # or that job bypasses this gate. ci-green: runs-on: ubuntu-latest - needs: [fmt, clippy, test, docs, check-workshop, check-workshop-linux, ui, msrv, supply-chain] + needs: [fmt, clippy, test, docs, check-workshop, check-workshop-linux, ui, supply-chain] if: always() steps: - name: Verify every job succeeded diff --git a/.github/workflows/release-workshop.yml b/.github/workflows/release-workshop.yml index b05f74616..c392a6849 100644 --- a/.github/workflows/release-workshop.yml +++ b/.github/workflows/release-workshop.yml @@ -26,11 +26,6 @@ concurrency: group: release-workshop-${{ github.ref_name }} cancel-in-progress: false -env: - # The repo's rust-toolchain.toml pins the MSRV for local builds; CI tests - # the current stable. - RUSTUP_TOOLCHAIN: stable - jobs: prepare: runs-on: ubuntu-latest diff --git a/.github/workflows/stt-miri.yml b/.github/workflows/stt-miri.yml index 0ffabd4df..fb402c775 100644 --- a/.github/workflows/stt-miri.yml +++ b/.github/workflows/stt-miri.yml @@ -46,16 +46,9 @@ jobs: native-whisper: runs-on: [self-hosted, windows, cuda] timeout-minutes: 90 - env: - RUSTUP_TOOLCHAIN: 1.89 - RUSTUP_AUTO_INSTALL: "0" steps: - uses: actions/checkout@v4 - - name: Verify preinstalled MSRV Rust - shell: powershell - run: tools/validate-rust-1.89.0.ps1 - - name: Cache Cargo uses: Swatinem/rust-cache@v2 diff --git a/AGENTS.md b/AGENTS.md index 18f83ea38..9c5721f2e 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -12,4 +12,3 @@ Multi-crate Rust workspace for the PromptForge pipeline runtime, inference gatew - Long-running work reports through `shared-progress`. Producers report operation state, hosts forward it, and renderers format it. - Unsafe code stays in its explicitly owned boundary. Every unsafe block documents its safety invariants immediately before the block. - Comments explain a non-obvious constraint, ordering requirement, or workaround. Every platform or external-bug workaround cites its upstream issue URL in the explanatory comment. -- CI lints with a newer stable toolchain than the pinned local MSRV (`rust-toolchain.toml`). Lints introduced or strengthened after the pin, such as unused glob imports, may fail only on CI. Check with the newer toolchain before pushing when a change touches imports or doc comments; `cargo doc` runs with warnings denied on CI. diff --git a/Cargo.toml b/Cargo.toml index 9892cf57c..4b8d86fab 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -13,7 +13,6 @@ default-members = ["crates/gateway"] [workspace.package] version = "0.3.0" edition = "2024" -rust-version = "1.89" license = "BSL-1.0" repository = "https://github.com/cppalliance/promptforge" diff --git a/README.md b/README.md index 7ec41fa83..2a3232d54 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,5 @@ [![CI](https://github.com/cppalliance/promptforge/actions/workflows/ci.yml/badge.svg)](https://github.com/cppalliance/promptforge/actions/workflows/ci.yml) [![License](https://img.shields.io/badge/license-BSL--1.0-blue.svg)](LICENSE) -[![Rust](https://img.shields.io/badge/rust-1.89%2B-orange.svg)](https://www.rust-lang.org/) # PromptForge @@ -65,7 +64,7 @@ Lua sets up the turn. The prose before a Lua block is that block's lazy `prose` ## Build from source -Every build needs Rust 1.89 or later and Node.js 22. The two web UIs are bundled with esbuild during the Cargo build, so run `npm ci` once in each `ui/` folder after cloning: +Every build needs Rust and Node.js 22. The two web UIs are bundled with esbuild during the Cargo build, so run `npm ci` once in each `ui/` folder after cloning: ```bash git clone git@github.com:cppalliance/promptforge.git @@ -109,10 +108,6 @@ Build the guide locally with `mdbook build guide`. ![Filing cabinets](images/banner-06.png) -## Minimum Rust Version - -Rust 1.89 or later. - ## Contributing Build, format, and test before you open a PR. CI runs `cargo fmt --check`, `clippy -D warnings`, and `cargo test --workspace`. diff --git a/crates/build-llama-cuda/Cargo.toml b/crates/build-llama-cuda/Cargo.toml index 97b492a43..1e38afa57 100644 --- a/crates/build-llama-cuda/Cargo.toml +++ b/crates/build-llama-cuda/Cargo.toml @@ -2,7 +2,6 @@ name = "build-llama-cuda" version.workspace = true edition.workspace = true -rust-version.workspace = true license.workspace = true repository.workspace = true publish = false diff --git a/crates/build-ui/Cargo.toml b/crates/build-ui/Cargo.toml index 630dc1448..db477f4e2 100644 --- a/crates/build-ui/Cargo.toml +++ b/crates/build-ui/Cargo.toml @@ -2,7 +2,6 @@ name = "build-ui" version.workspace = true edition.workspace = true -rust-version.workspace = true license.workspace = true repository.workspace = true publish = false diff --git a/crates/build-workshop/Cargo.toml b/crates/build-workshop/Cargo.toml index 94154afc6..e4955242f 100644 --- a/crates/build-workshop/Cargo.toml +++ b/crates/build-workshop/Cargo.toml @@ -2,7 +2,6 @@ name = "build-workshop" version.workspace = true edition.workspace = true -rust-version.workspace = true license.workspace = true repository.workspace = true publish = false diff --git a/crates/gateway-config-ui/Cargo.toml b/crates/gateway-config-ui/Cargo.toml index 18af9f1f7..864a9733f 100644 --- a/crates/gateway-config-ui/Cargo.toml +++ b/crates/gateway-config-ui/Cargo.toml @@ -2,7 +2,6 @@ name = "gateway-config-ui" version.workspace = true edition.workspace = true -rust-version.workspace = true license.workspace = true repository.workspace = true publish = false diff --git a/crates/gateway-config/Cargo.toml b/crates/gateway-config/Cargo.toml index 2134acd42..1bbfa27f7 100644 --- a/crates/gateway-config/Cargo.toml +++ b/crates/gateway-config/Cargo.toml @@ -2,7 +2,6 @@ name = "gateway-config" version.workspace = true edition.workspace = true -rust-version.workspace = true license.workspace = true repository.workspace = true publish = false diff --git a/crates/gateway-local/Cargo.toml b/crates/gateway-local/Cargo.toml index 83464c56e..faecf06d5 100644 --- a/crates/gateway-local/Cargo.toml +++ b/crates/gateway-local/Cargo.toml @@ -2,7 +2,6 @@ name = "gateway-local" version.workspace = true edition.workspace = true -rust-version.workspace = true license.workspace = true repository.workspace = true publish = false diff --git a/crates/gateway-logging/Cargo.toml b/crates/gateway-logging/Cargo.toml index ed3095493..c7c958845 100644 --- a/crates/gateway-logging/Cargo.toml +++ b/crates/gateway-logging/Cargo.toml @@ -2,7 +2,6 @@ name = "gateway-logging" version.workspace = true edition.workspace = true -rust-version.workspace = true license.workspace = true repository.workspace = true publish = false diff --git a/crates/gateway-routing/Cargo.toml b/crates/gateway-routing/Cargo.toml index 0042a07c6..907c2be33 100644 --- a/crates/gateway-routing/Cargo.toml +++ b/crates/gateway-routing/Cargo.toml @@ -2,7 +2,6 @@ name = "gateway-routing" version.workspace = true edition.workspace = true -rust-version.workspace = true license.workspace = true repository.workspace = true publish = false diff --git a/crates/gateway-stt-backend-whisper/Cargo.toml b/crates/gateway-stt-backend-whisper/Cargo.toml index 2a02d34ab..890cf93c9 100644 --- a/crates/gateway-stt-backend-whisper/Cargo.toml +++ b/crates/gateway-stt-backend-whisper/Cargo.toml @@ -2,7 +2,6 @@ name = "gateway-stt-backend-whisper" version.workspace = true edition.workspace = true -rust-version.workspace = true license.workspace = true repository.workspace = true publish = false diff --git a/crates/gateway-stt-engine/Cargo.toml b/crates/gateway-stt-engine/Cargo.toml index cefee55a5..724e6445c 100644 --- a/crates/gateway-stt-engine/Cargo.toml +++ b/crates/gateway-stt-engine/Cargo.toml @@ -2,7 +2,6 @@ name = "gateway-stt-engine" version.workspace = true edition.workspace = true -rust-version.workspace = true license.workspace = true repository.workspace = true publish = false diff --git a/crates/gateway-stt-engine/src/test_fixtures/tests/scenario_cleanup/decode.rs b/crates/gateway-stt-engine/src/test_fixtures/tests/scenario_cleanup/decode.rs index 5fe224321..637d4f094 100644 --- a/crates/gateway-stt-engine/src/test_fixtures/tests/scenario_cleanup/decode.rs +++ b/crates/gateway-stt-engine/src/test_fixtures/tests/scenario_cleanup/decode.rs @@ -71,7 +71,7 @@ async fn canceled_decode_scenario_releases_and_permits_a_follow_up() { engine.shutdown().expect("worker joins"); } -#[tokio::test(start_paused = true)] +#[tokio::test] async fn decode_rendezvous_timeout_releases_a_late_arrival_and_permits_a_follow_up() { let decoder = ScriptedDecoder::new(); decoder.push_text("late arrival"); diff --git a/crates/gateway-stt/Cargo.toml b/crates/gateway-stt/Cargo.toml index ff3c3ed37..d2d0b5c41 100644 --- a/crates/gateway-stt/Cargo.toml +++ b/crates/gateway-stt/Cargo.toml @@ -2,7 +2,6 @@ name = "gateway-stt" version.workspace = true edition.workspace = true -rust-version.workspace = true license.workspace = true repository.workspace = true publish = false diff --git a/crates/gateway-stt/src/audio.rs b/crates/gateway-stt/src/audio.rs index ffc97a925..5631cf896 100644 --- a/crates/gateway-stt/src/audio.rs +++ b/crates/gateway-stt/src/audio.rs @@ -314,7 +314,7 @@ mod tests { assert!( error .source() - .is_some_and(<(dyn std::error::Error + 'static)>::is::), + .is_some_and(::is::), "the error chain carries the decoder failure" ); } diff --git a/crates/gateway-web-search/Cargo.toml b/crates/gateway-web-search/Cargo.toml index 0061abc00..abbfe8455 100644 --- a/crates/gateway-web-search/Cargo.toml +++ b/crates/gateway-web-search/Cargo.toml @@ -2,7 +2,6 @@ name = "gateway-web-search" version.workspace = true edition.workspace = true -rust-version.workspace = true license.workspace = true repository.workspace = true publish = false diff --git a/crates/gateway-whisper-ffi/Cargo.toml b/crates/gateway-whisper-ffi/Cargo.toml index 144e9f7f0..e5195beaa 100644 --- a/crates/gateway-whisper-ffi/Cargo.toml +++ b/crates/gateway-whisper-ffi/Cargo.toml @@ -2,7 +2,6 @@ name = "gateway-whisper-ffi" version.workspace = true edition.workspace = true -rust-version.workspace = true license.workspace = true repository.workspace = true publish = false diff --git a/crates/gateway/Cargo.toml b/crates/gateway/Cargo.toml index f33fc5894..f383e2dac 100644 --- a/crates/gateway/Cargo.toml +++ b/crates/gateway/Cargo.toml @@ -2,7 +2,6 @@ name = "gateway" version.workspace = true edition.workspace = true -rust-version.workspace = true license.workspace = true repository.workspace = true publish = false diff --git a/crates/product-integration-tests/Cargo.toml b/crates/product-integration-tests/Cargo.toml index e27c7bdf7..25ebe4c24 100644 --- a/crates/product-integration-tests/Cargo.toml +++ b/crates/product-integration-tests/Cargo.toml @@ -2,7 +2,6 @@ name = "product-integration-tests" version.workspace = true edition.workspace = true -rust-version.workspace = true license.workspace = true repository.workspace = true publish = false diff --git a/crates/promptforge-agent/Cargo.toml b/crates/promptforge-agent/Cargo.toml index b8dfa9612..791181a78 100644 --- a/crates/promptforge-agent/Cargo.toml +++ b/crates/promptforge-agent/Cargo.toml @@ -2,7 +2,6 @@ name = "promptforge-agent" version.workspace = true edition.workspace = true -rust-version.workspace = true license.workspace = true repository.workspace = true publish = false diff --git a/crates/promptforge-core-support/Cargo.toml b/crates/promptforge-core-support/Cargo.toml index b9b878ddc..7b72dc5e0 100644 --- a/crates/promptforge-core-support/Cargo.toml +++ b/crates/promptforge-core-support/Cargo.toml @@ -2,7 +2,6 @@ name = "promptforge-core-support" version.workspace = true edition.workspace = true -rust-version.workspace = true license.workspace = true repository.workspace = true publish = false diff --git a/crates/promptforge-core/Cargo.toml b/crates/promptforge-core/Cargo.toml index bd4926076..c1023e474 100644 --- a/crates/promptforge-core/Cargo.toml +++ b/crates/promptforge-core/Cargo.toml @@ -2,7 +2,6 @@ name = "promptforge-core" version.workspace = true edition.workspace = true -rust-version.workspace = true license.workspace = true repository.workspace = true publish = false diff --git a/crates/promptforge-lua/Cargo.toml b/crates/promptforge-lua/Cargo.toml index d825bac18..06fce1ddf 100644 --- a/crates/promptforge-lua/Cargo.toml +++ b/crates/promptforge-lua/Cargo.toml @@ -2,7 +2,6 @@ name = "promptforge-lua" version.workspace = true edition.workspace = true -rust-version.workspace = true license.workspace = true repository.workspace = true publish = false diff --git a/crates/promptforge-model-client/Cargo.toml b/crates/promptforge-model-client/Cargo.toml index 49ac259b4..dd7e3ff1a 100644 --- a/crates/promptforge-model-client/Cargo.toml +++ b/crates/promptforge-model-client/Cargo.toml @@ -2,7 +2,6 @@ name = "promptforge-model-client" version.workspace = true edition.workspace = true -rust-version.workspace = true license.workspace = true repository.workspace = true publish = false diff --git a/crates/promptforge-parser/Cargo.toml b/crates/promptforge-parser/Cargo.toml index 01c7cefb4..4d9338de3 100644 --- a/crates/promptforge-parser/Cargo.toml +++ b/crates/promptforge-parser/Cargo.toml @@ -2,7 +2,6 @@ name = "promptforge-parser" version.workspace = true edition.workspace = true -rust-version.workspace = true license.workspace = true repository.workspace = true publish = false diff --git a/crates/promptforge-store/Cargo.toml b/crates/promptforge-store/Cargo.toml index fe3cd3d23..7b4112c89 100644 --- a/crates/promptforge-store/Cargo.toml +++ b/crates/promptforge-store/Cargo.toml @@ -2,7 +2,6 @@ name = "promptforge-store" version.workspace = true edition.workspace = true -rust-version.workspace = true license.workspace = true repository.workspace = true publish = false diff --git a/crates/promptforge-tool-picker/Cargo.toml b/crates/promptforge-tool-picker/Cargo.toml index 37da56cc8..55e735021 100644 --- a/crates/promptforge-tool-picker/Cargo.toml +++ b/crates/promptforge-tool-picker/Cargo.toml @@ -2,7 +2,6 @@ name = "promptforge-tool-picker" version.workspace = true edition.workspace = true -rust-version.workspace = true license.workspace = true repository.workspace = true publish = false diff --git a/crates/promptforge-tools/Cargo.toml b/crates/promptforge-tools/Cargo.toml index 7c7799da7..5f5e21589 100644 --- a/crates/promptforge-tools/Cargo.toml +++ b/crates/promptforge-tools/Cargo.toml @@ -2,7 +2,6 @@ name = "promptforge-tools" version.workspace = true edition.workspace = true -rust-version.workspace = true license.workspace = true repository.workspace = true publish = false diff --git a/crates/promptforge-web-search/Cargo.toml b/crates/promptforge-web-search/Cargo.toml index 81bad464a..e264dd049 100644 --- a/crates/promptforge-web-search/Cargo.toml +++ b/crates/promptforge-web-search/Cargo.toml @@ -2,7 +2,6 @@ name = "promptforge-web-search" version.workspace = true edition.workspace = true -rust-version.workspace = true license.workspace = true repository.workspace = true publish = false diff --git a/crates/promptforge-webfetch/Cargo.toml b/crates/promptforge-webfetch/Cargo.toml index ea5f8d298..439267ae7 100644 --- a/crates/promptforge-webfetch/Cargo.toml +++ b/crates/promptforge-webfetch/Cargo.toml @@ -2,7 +2,6 @@ name = "promptforge-webfetch" version.workspace = true edition.workspace = true -rust-version.workspace = true license.workspace = true repository.workspace = true publish = false diff --git a/crates/promptforge/Cargo.toml b/crates/promptforge/Cargo.toml index a30bd618a..1cf133608 100644 --- a/crates/promptforge/Cargo.toml +++ b/crates/promptforge/Cargo.toml @@ -2,7 +2,6 @@ name = "promptforge" version.workspace = true edition.workspace = true -rust-version.workspace = true license.workspace = true repository.workspace = true publish = false diff --git a/crates/shared-loopback/Cargo.toml b/crates/shared-loopback/Cargo.toml index baefe44d4..0d90f25e2 100644 --- a/crates/shared-loopback/Cargo.toml +++ b/crates/shared-loopback/Cargo.toml @@ -2,7 +2,6 @@ name = "shared-loopback" version.workspace = true edition.workspace = true -rust-version.workspace = true license.workspace = true repository.workspace = true publish = false diff --git a/crates/shared-progress/Cargo.toml b/crates/shared-progress/Cargo.toml index d061dc596..470719c94 100644 --- a/crates/shared-progress/Cargo.toml +++ b/crates/shared-progress/Cargo.toml @@ -2,7 +2,6 @@ name = "shared-progress" version.workspace = true edition.workspace = true -rust-version.workspace = true license.workspace = true repository.workspace = true publish = false diff --git a/crates/shared-protocol/Cargo.toml b/crates/shared-protocol/Cargo.toml index 1987be118..83582ebc4 100644 --- a/crates/shared-protocol/Cargo.toml +++ b/crates/shared-protocol/Cargo.toml @@ -2,7 +2,6 @@ name = "shared-protocol" version.workspace = true edition.workspace = true -rust-version.workspace = true license.workspace = true repository.workspace = true publish = false diff --git a/crates/shared-sidecar/Cargo.toml b/crates/shared-sidecar/Cargo.toml index 91912cb07..97f9f1c9c 100644 --- a/crates/shared-sidecar/Cargo.toml +++ b/crates/shared-sidecar/Cargo.toml @@ -2,7 +2,6 @@ name = "shared-sidecar" version.workspace = true edition.workspace = true -rust-version.workspace = true license.workspace = true repository.workspace = true publish = false diff --git a/crates/workshop-server/Cargo.toml b/crates/workshop-server/Cargo.toml index 248361982..3fa5c5366 100644 --- a/crates/workshop-server/Cargo.toml +++ b/crates/workshop-server/Cargo.toml @@ -2,7 +2,6 @@ name = "workshop-server" version.workspace = true edition.workspace = true -rust-version.workspace = true license.workspace = true repository.workspace = true publish = false diff --git a/crates/workshop/Cargo.toml b/crates/workshop/Cargo.toml index 382ccd44d..4e27a9220 100644 --- a/crates/workshop/Cargo.toml +++ b/crates/workshop/Cargo.toml @@ -2,7 +2,6 @@ name = "workshop" version.workspace = true edition.workspace = true -rust-version.workspace = true license.workspace = true repository.workspace = true publish = false diff --git a/crates/workshop/src/bridge.rs b/crates/workshop/src/bridge.rs index 0d5469bbc..9b6d1de59 100644 --- a/crates/workshop/src/bridge.rs +++ b/crates/workshop/src/bridge.rs @@ -202,8 +202,7 @@ fn dropped_paths(args: &ICoreWebView2WebMessageReceivedEventArgs) -> Vec key.toLowerCase() === name.toLowerCase(), - ); -} - -function setEnvironmentVariable(environment, name, value) { - const key = environmentKey(environment, name) ?? name; - environment[key] = value; -} - -function deleteEnvironmentVariable(environment, name) { - const key = environmentKey(environment, name); - if (key) { - delete environment[key]; - } -} - -function createToolLayout(names = ["cargo", "rustc"]) { - const bin = mkdtempSync(join(fixtureRoot, "bin-")); - for (const name of names) { - const destination = join(bin, `${name}.exe`); - copyFileSync(fakeTool, destination); - chmodSync(destination, 0o755); - } - return bin; -} - -function runValidator({ - bin, - cargoVersion = "1.89.0", - contract = bin, - rustcVersion = "1.89.0", -} = {}) { - const environment = { ...process.env }; - const runRoot = mkdtempSync(join(fixtureRoot, "run-")); - const githubPath = join(runRoot, "github-path"); - const invocationLog = join(runRoot, "invocations"); - - deleteEnvironmentVariable(environment, "PROMPTFORGE_RUST_1_89_0_BIN"); - if (contract !== undefined) { - setEnvironmentVariable( - environment, - "PROMPTFORGE_RUST_1_89_0_BIN", - contract, - ); - } - setEnvironmentVariable(environment, "GITHUB_PATH", githubPath); - setEnvironmentVariable(environment, "FAKE_INVOCATION_LOG", invocationLog); - setEnvironmentVariable(environment, "FAKE_CARGO_VERSION", cargoVersion); - setEnvironmentVariable(environment, "FAKE_RUSTC_VERSION", rustcVersion); - - const powershell = process.platform === "win32" - ? join( - process.env.SystemRoot ?? "C:\\WINDOWS", - "System32", - "WindowsPowerShell", - "v1.0", - "powershell.exe", - ) - : "pwsh"; - const result = spawnSync( - powershell, - ["-NoProfile", "-NonInteractive", "-File", validator], - { - cwd: root, - encoding: "utf8", - env: environment, - timeout: 30_000, - }, - ); - return { - ...result, - githubPath, - invocationLog, - output: `${result.stdout ?? ""}${result.stderr ?? ""}`.replace(/\s+/g, " "), - }; -} - -before(() => { - writeFileSync( - fakeToolSource, - String.raw`use std::env; -use std::fs::OpenOptions; -use std::io::Write; - -fn main() { - let name = env::current_exe() - .expect("current executable") - .file_stem() - .expect("executable stem") - .to_string_lossy() - .to_ascii_lowercase(); - let arguments: Vec<_> = env::args().skip(1).collect(); - let mut log = OpenOptions::new() - .append(true) - .create(true) - .open(env::var("FAKE_INVOCATION_LOG").expect("invocation log")) - .expect("open invocation log"); - writeln!(log, "{name} {}", arguments.join(" ")).expect("write invocation log"); - let version = match name.as_str() { - "cargo" => env::var("FAKE_CARGO_VERSION").expect("cargo version"), - "rustc" => env::var("FAKE_RUSTC_VERSION").expect("rustc version"), - _ => panic!("unexpected fake tool name: {name}"), - }; - println!("{name} {version} (fixture 2026-09-08)"); -} -`, - ); - const compiled = spawnSync("rustc", [fakeToolSource, "-o", fakeTool], { - cwd: root, - encoding: "utf8", - timeout: 30_000, - }); - assert.equal( - compiled.status, - 0, - `failed to compile fake Rust tools:\n${compiled.stdout}${compiled.stderr}`, - ); -}); - -after(() => { - rmSync(fixtureRoot, { force: true, recursive: true }); -}); - -test("accepts valid direct Rust tools", () => { - const bin = createToolLayout(); - const result = runValidator({ bin }); - - assert.equal(result.status, 0, result.output); - assert.match(result.stdout, /Validated cargo 1\.89\.0 from /); - assert.match(result.stdout, /Validated rustc 1\.89\.0 from /); - assert.equal(readFileSync(result.githubPath, "utf8").trim(), resolve(bin)); -}); - -test("never passes a toolchain selector to either executable", () => { - const bin = createToolLayout(); - const result = runValidator({ bin }); - - assert.equal(result.status, 0, result.output); - assert.deepEqual( - readFileSync(result.invocationLog, "utf8").trim().split(/\r?\n/), - ["cargo --version", "rustc --version"], - ); -}); - -test("rejects an unset contract instead of discovering Rust", () => { - const result = runValidator({ contract: undefined }); - - assert.notEqual(result.status, 0); - assert.match(result.output, /PROMPTFORGE_RUST_1_89_0_BIN must be set/); -}); - -test("rejects relative and nonexistent contract directories", () => { - for (const [contract, message] of [ - [join("relative", "rust-bin"), /must be an absolute directory/], - [join(fixtureRoot, "does-not-exist"), /directory does not exist/], - ]) { - const result = runValidator({ contract }); - assert.notEqual(result.status, 0); - assert.match(result.output, message); - } -}); - -test("rejects missing or non-file executables", () => { - const missingRustc = createToolLayout(["cargo"]); - const nonFileCargo = createToolLayout(["rustc"]); - mkdirSync(join(nonFileCargo, "cargo.exe")); - - for (const bin of [missingRustc, nonFileCargo]) { - const result = runValidator({ bin }); - assert.notEqual(result.status, 0); - assert.match(result.output, /must contain regular cargo\.exe and rustc\.exe files/); - } -}); - -test("rejects malformed tool version output", () => { - for (const versions of [ - { cargoVersion: "stable" }, - { rustcVersion: "unknown" }, - ]) { - const result = runValidator({ bin: createToolLayout(), ...versions }); - assert.notEqual(result.status, 0); - assert.match(result.output, /returned malformed version output/); - } -}); - -test("rejects every wrong or mixed Rust version", () => { - for (const versions of [ - { cargoVersion: "1.90.0" }, - { rustcVersion: "1.88.0" }, - { cargoVersion: "1.89.0", rustcVersion: "1.90.0" }, - ]) { - const result = runValidator({ bin: createToolLayout(), ...versions }); - assert.notEqual(result.status, 0); - assert.match(result.output, /requires exactly 1\.89\.0/); - } -}); diff --git a/tools/validate-rust-1.89.0.ps1 b/tools/validate-rust-1.89.0.ps1 deleted file mode 100644 index 885ebef75..000000000 --- a/tools/validate-rust-1.89.0.ps1 +++ /dev/null @@ -1,66 +0,0 @@ -$ErrorActionPreference = 'Stop' - -$contractName = 'PROMPTFORGE_RUST_1_89_0_BIN' -$requiredVersion = '1.89.0' -$contractBin = [Environment]::GetEnvironmentVariable($contractName) - -if ([string]::IsNullOrWhiteSpace($contractBin)) { - throw "$contractName must be set to an absolute Rust $requiredVersion bin directory" -} - -$contractBin = $contractBin.Trim() -if (-not [IO.Path]::IsPathRooted($contractBin)) { - throw "$contractName must be an absolute directory: '$contractBin'" -} -if (-not (Test-Path -LiteralPath $contractBin -PathType Container)) { - throw "$contractName directory does not exist: '$contractBin'" -} -$contractBin = (Resolve-Path -LiteralPath $contractBin).Path - -$tools = @{} -foreach ($name in @('cargo', 'rustc')) { - $path = Join-Path $contractBin "$name.exe" - if (-not (Test-Path -LiteralPath $path -PathType Leaf)) { - throw "$contractName must contain regular cargo.exe and rustc.exe files: '$contractBin'" - } - $item = Get-Item -LiteralPath $path - if (($item.Attributes -band [IO.FileAttributes]::ReparsePoint) -ne 0) { - throw "$contractName must contain regular cargo.exe and rustc.exe files: '$contractBin'" - } - $tools[$name] = $item.FullName -} - -function Read-RustVersion { - param( - [Parameter(Mandatory = $true)][string] $Name, - [Parameter(Mandatory = $true)][string] $Path - ) - - $lines = @(& $Path '--version' 2>&1) - $exitCode = $LASTEXITCODE - $text = ($lines | Out-String).Trim() - if ($exitCode -ne 0) { - throw "$Name.exe failed at '$Path' with exit code ${exitCode}: $text" - } - $match = [regex]::Match( - $text, - "^$([regex]::Escape($Name))\s+(\d+\.\d+\.\d+)(?:\s|$)" - ) - if (-not $match.Success) { - throw "$Name.exe returned malformed version output at '$Path': $text" - } - $actualVersion = $match.Groups[1].Value - if ($actualVersion -ne $requiredVersion) { - throw "$Name.exe reports $actualVersion, but $contractName requires exactly $requiredVersion" - } - Write-Host "Validated $Name $actualVersion from $Path" -} - -Read-RustVersion -Name 'cargo' -Path $tools['cargo'] -Read-RustVersion -Name 'rustc' -Path $tools['rustc'] - -if (-not [string]::IsNullOrWhiteSpace($env:GITHUB_PATH)) { - $contractBin | Add-Content -LiteralPath $env:GITHUB_PATH -} - -exit 0 diff --git a/vibe-ledger.md b/vibe-ledger.md index 6a51f8f50..893e6a12c 100644 --- a/vibe-ledger.md +++ b/vibe-ledger.md @@ -68,3 +68,5 @@ - Rulebook debt tiers Step 5: FixtureError and typed sources in the take/finalization pipeline - focused verification: `cargo test -p gateway-stt -F test-fixtures` (125 lib + 53 integration, 0 failed), clippy `-D warnings`, fmt, and feature-off check green; review clean; no Verify dispatch (mid-component step with no fixes). Decision: take failure stored and shared as `Arc` since `pending_failure` clones it out of the mutex for session gating | Falsifier: if the failure were only ever moved, plain `TakeFailure` channels suffice. Decision: `PendingPrecommitFailure` carries `Arc` without `#[source]`; thiserror 2.0.19 `AsDynError` has no `Arc` impl | Falsifier: a thiserror release supporting Arc sources. Decision: `FixtureError` boxes pub(crate) sources as `Box`; `SpeechError`/`serde_json::Error` carried concretely | Falsifier: making `SessionError`/`RegisterError`/`ClientError` public. Decision: `ItemFailure` stays String-carrying (Clone+Eq wire terminal) but is classified from the typed `TakeFailure` | Falsifier: the wire protocol gains typed failure codes. - Rulebook debt tiers Step 6: anyhow for test and build code, typed errors in remaining production - FULL gate pass: build, fmt, clippy (pinned and newer stable), docs, full workspace tests, workshop nextest, and doctests all green (verify-step-6-round-1.log); review clean including the cumulative component diff. Decision: build-ui uses anyhow, not a new thiserror type | Falsifier: the contract allows exactly one new public error type (FixtureError) and routes build tooling to anyhow. Decision: menu.rs gains private `SwitchFailure` with Display byte-identical to the old strings | Falsifier: no existing workshop-server enum models switch lifecycle. Decision: dialect.rs gains private `ToolCallRejection` with Display matching the old wire warnings exactly | Falsifier: the reasons become `gateway_warning` wire strings, so the text had to survive. Decision: confine.rs `parse_whoami_user_sid` returns `LocalError::CacheNotPrivate` directly | Falsifier: keeps the existing variant and message while dropping the String channel. Decision: the two LazyLock statics hold `SharedSource`, replayed via a new `Error::shared` helper | Falsifier: SharedSource exists precisely for re-producing typed errors from non-Clone caches. + +- Unify toolchain Step 1: Unify toolchain, fix the three CI failures, and remove MSRV machinery - focused verify pass: `cargo build` and `cargo nextest run -p gateway-stt-engine --all-features decode_rendezvous_timeout` green (verify-step-1-round-1.log); both clippy scopes and fmt clean during coding. Decision: rewrote the `rust-toolchain.toml` comment, which described the removed MSRV pin | Falsifier: the old comment claims a pin and CI overrides that no longer exist. Decision: left "Rust 1.89 or later" in five crate README.md files untouched (outside the step's enumerated files) | Falsifier: plan step or contract names them. Decision: split four out-of-scope `Duration::from_mins`/`from_hours` hunks into a separate commit (`c1ef217f`) rather than amending the step to name them | Falsifier: the plan file is treated as amendable after review, making the split commit redundant history. diff --git a/vibe/2026-09-11-2-unify-toolchain.md b/vibe/2026-09-11-2-unify-toolchain.md new file mode 100644 index 000000000..8e86917df --- /dev/null +++ b/vibe/2026-09-11-2-unify-toolchain.md @@ -0,0 +1,157 @@ +--- +name: Unify toolchain and fix CI +overview: Remove the MSRV/local-vs-CI toolchain split so local builds and CI use the same stable Rust, and fix the three CI failures the skew caused. +todos: + - id: fix-ci-failures + content: Fix audio.rs:317 parens, bridge.rs expect lints, decode.rs flaky test + status: pending + - id: unify-toolchain-files + content: rust-toolchain.toml to stable, remove rust-version from workspace and all 34 crates + status: pending + - id: simplify-ci-yml + content: Remove RUSTUP_TOOLCHAIN overrides and msrv job from ci.yml + status: pending + - id: simplify-other-workflows + content: Remove MSRV env and validation step from stt-miri.yml, remove from release-workshop.yml + status: pending + - id: docs-and-cleanup + content: Remove skew bullet from AGENTS.md, MSRV section from README.md, delete validation script and its test + status: pending + - id: verify + content: Run full gate locally on stable, push, confirm CI green + status: pending +isProject: false +--- + +# Unify toolchain and fix CI + + + +## Product Requirements + +The repository pins local builds to Rust 1.89 while CI lints and tests on stable (currently 1.98). This skew just caused three CI failures on a pull request that passed every local check. Every crate in the workspace is `publish = false`, so no downstream consumer needs an MSRV guarantee. + +- Problem and users: the developer (and their self-hosted runner, which is the same machine) cannot trust that green locally means green on CI. +- Goals: local builds and CI use the same Rust toolchain; the three current CI failures are fixed; the MSRV machinery is removed. +- Non-goals: changing the Miri nightly pin (that pins a specific nightly for diagnostic text stability, a different concern); supporting consumers who build from crates.io (there are none). +- Success criteria: the full verification gate passes locally on stable and CI goes green on the same toolchain. +- Constraints: the self-hosted runner must track stable without manual intervention after the change; the `rust-version` field's resolver side effect (preferring dependency versions compatible with the declared MSRV) is accepted as going away. +- Open questions: none. + +## Functional Specification + +The developer runs the same commands locally that CI runs, on the same toolchain, so a green local gate predicts a green CI run instead of merely suggesting it. The three current CI failures disappear as a side effect of removing the skew that produced them, not as patches layered on top of it. A contributor cloning the repository gets the right toolchain from rustup automatically - no version number to look up, no MSRV policy to learn, no validation script to run. + +- Actors and workflows: the developer builds and tests locally; CI runs the same checks on every pull request. +- Inputs and outputs: code changes go in; a green or red CI status comes out. +- States and validation: before, local and CI can disagree on lint and test outcomes; after, they cannot disagree on toolchain behavior. +- Errors and recovery: the three current failures (a clippy lint, a stale lint expectation, a flaky paused-time test) are fixed in place. +- Security and privacy behavior: none. +- Acceptance criteria: `cargo fmt --all --check`, `cargo clippy --workspace --all-targets --all-features -- -D warnings`, `cargo test --locked --workspace --all-features`, `cargo test --doc`, and `RUSTDOCFLAGS="-D warnings" cargo doc --no-deps --all-features` all pass locally on stable, and the CI run on the same commit is green. + + + + +## Technical Design + +The change removes the MSRV pin and its CI overrides, then fixes the three code issues the skew exposed. + +- `rust-toolchain.toml`: `channel = "1.89"` becomes `channel = "stable"`. +- `Cargo.toml`: remove `rust-version = "1.89"` from `[workspace.package]`. +- All 34 crate manifests under `crates/`: remove `rust-version.workspace = true` (line 5 of each). +- `.github/workflows/ci.yml`: remove the global `env.RUSTUP_TOOLCHAIN: stable` (line 19) and every per-job `env.RUSTUP_TOOLCHAIN: stable` (lines 37, 87, 136, 166, 238); delete the entire `msrv` job (lines 344-372); remove `msrv` from `ci-green`'s `needs:` list (line 394). +- `.github/workflows/stt-miri.yml`: remove `env.RUSTUP_TOOLCHAIN: 1.89` and `env.RUSTUP_AUTO_INSTALL: "0"` from `native-whisper` (lines 49-51); remove the "Verify preinstalled MSRV Rust" step (lines 55-57). The Miri job's `nightly-2026-09-05` pin stays. +- `.github/workflows/release-workshop.yml`: remove the MSRV comment (lines 30-31) and `RUSTUP_TOOLCHAIN: stable` (line 32). +- `AGENTS.md`: remove the last bullet about CI linting on a newer stable than the pinned local MSRV. +- `README.md`: remove the "Minimum Rust Version" section (lines 112-114); change "Every build needs Rust 1.89 or later and Node.js 22" to "Every build needs Rust and Node.js 22" (line 68); remove or update the `rust-1.89+` badge (line 3). +- Delete `tools/validate-rust-1.89.0.ps1` and `tools/check-stt-native-workflow.test.mjs` (the test exists only to test the script). +- `crates/gateway-stt/src/audio.rs:317`: remove unnecessary parens - `<(dyn std::error::Error + 'static)>::is::<...>` becomes `::is::<...>`. +- `crates/workshop/src/bridge.rs`: replace `#[expect(clippy::ptr_as_ptr, clippy::borrow_as_ptr)]` with `#[expect(clippy::ref_as_ptr)]` on the `#[implement(...)]` macro blocks (the old lints were replaced by `ref_as_ptr` in Rust 1.98). +- `crates/gateway-stt-engine/src/test_fixtures/tests/scenario_cleanup/decode.rs`: revert the `decode_rendezvous_timeout` test to real-time (remove `start_paused = true`); the paused clock is structurally incompatible with the test's real-time condvar rendezvous. + + + + +## Testing Plan + +The full verification gate runs locally on stable after the changes, then CI confirms on the same toolchain. + +- Unit: the existing suite passes unmodified except for the three fixed files. +- Integration and end-to-end: the full workspace test suite passes on stable. +- Regression, security, and performance: the three CI failures are fixed and do not recur; the `ci-green` aggregate job reflects the remaining jobs accurately after `msrv` is removed from its `needs:` list. +- Exit criteria: `cargo fmt --all --check`, `cargo clippy --workspace --all-targets --all-features -- -D warnings`, `cargo test --locked --workspace --all-features`, `cargo test --doc`, and `RUSTDOCFLAGS="-D warnings" cargo doc --no-deps --all-features` all pass locally on stable, and the CI run on the same commit is green. + + + + +## Decision Record + +- Decisions: + - Unify on `stable` rather than pinning to an exact version like `1.98`: "I want a rule: local builds and CI use the same environment. I never want CI to fail when local succeeds." Pinning to an exact version would require periodic bumps; `stable` tracks automatically. + - Remove `rust-version` entirely rather than setting it to match stable: "should we get rid of all this msrv and surrounding crap? I want a dramatic simplification to the build and CI." Every crate is `publish = false`, so the field has no consumer audience. + - Delete the `msrv` CI job rather than keeping it as a lower-bound check: the job exists to verify the MSRV, which no longer exists. + - Revert the `decode_rendezvous_timeout` test to real-time rather than fixing the paused-clock interaction: the test's condvar rendezvous runs on real OS threads, making paused time structurally incompatible. +- Rejected alternatives: + - Pin to an exact stable version (e.g., `1.98`): rejected because it requires manual bumps and reintroduces a drift window; revisit if a future stable release breaks the build in a way that needs pinning. + - Keep `rust-version` set to the current stable: rejected because it would drift and serve no consumer; revisit if the project ever publishes a crate. +- Assumptions, risks, and notes: + - The self-hosted runner needs `rustup default stable` run once after this change; after that, rustup tracks stable automatically. + - Removing `rust-version` means Cargo's resolver may select dependency versions that require newer Rust than any previously declared MSRV; on stable, this is always satisfied. + - A contributor on an old Rust who clones the repo gets `stable` from rustup automatically via the toolchain file; no compile error, no manual install. + + + + +## Project Survey + +- Status: complete +- Build command: `cargo build` (builds the gateway, the default member); `cargo build -p workshop` for the desktop app (explicit opt-in; needs Tauri system packages and `npm ci --prefix crates/workshop-server/ui` plus `npm ci --prefix crates/gateway-config-ui/ui` first) +- Focused test command pattern: `cargo nextest run -p ` or `cargo test -p --test it ` (integration binaries are named `it`) +- Component test command pattern: `cargo nextest run -p ` (all features: add `--all-features`) +- Full-suite test command: `cargo nextest run --locked --workspace --exclude workshop --exclude workshop-server --all-features`, then `cargo test --workspace --exclude workshop --exclude workshop-server --all-features --doc` for doctests; workshop crates run separately with `cargo nextest run --locked -p workshop -p workshop-server` +- Linter command: `cargo clippy --workspace --exclude workshop --exclude workshop-server --all-targets --all-features -- -D warnings`; workshop crates: `cargo clippy -p workshop -p workshop-server --all-targets -- -D warnings` +- Formatter check command: `cargo fmt --all --check` +- Docs command: `cargo doc --workspace --no-deps --all-features --exclude workshop --exclude workshop-server` with `RUSTDOCFLAGS: -D warnings` +- Test placement and naming conventions: unit tests live in `src/tests.rs` or `#[cfg(test)]` modules beside the code; integration tests live in `tests/it/` as one binary (`main.rs` plus per-area modules such as `boot.rs`, `chat.rs`); behavior tests ship in the same change as behavior changes +- Directory map: `crates/` holds all workspace members (Rust crates plus `shared-ui`, a TypeScript+CSS package excluded from the Cargo glob); `guide/` holds the user guide sources; `prompts/` holds prompt pipelines; `tools/` holds Node maintenance scripts (sidecar staging, workflow checks); `vibe/` holds `archdoc.md` architecture documentation; `images/` and `local/` hold assets and local config; `.github/workflows/` holds CI; `target/` and `target-msrv/` are build outputs +- Component boundaries: executor (`promptforge`, `promptforge-core`, `promptforge-lua`, `promptforge-parser`, `promptforge-agent`, `promptforge-tools`, `promptforge-store`, `promptforge-webfetch`, `promptforge-web-search`, `promptforge-model-client`, `promptforge-tool-picker`) executes pipelines and Lua programs; `gateway*` crates own model routing, provider access, STT, and local inference as an independent server; `workshop` and `workshop-server` are the Tauri desktop shell and in-process server; `shared-*` crates are the dependency-free substrate (progress, loopback, protocol, sidecar). Dependency rules: PromptForge crates cannot depend on Gateway or Workshop crates; Gateway crates cannot depend on PromptForge or Workshop crates; Workshop crates cannot depend on Gateway crates +- Conventions summary: Rust edition 2024, MSRV 1.89 pinned in `rust-toolchain.toml` while CI lints and tests on stable; `unsafe_code` forbidden workspace-wide with documented exceptions at owned boundaries; clippy `all` denied, `pedantic` warned, `unwrap_used`/`expect_used` denied; long-running work reports through `shared-progress`; comments explain non-obvious constraints and cite upstream issue URLs for workarounds; UI bundles build into `OUT_DIR` and no build step may dirty the repository tree + + + + +## Execution Instructions + + + +### Step 1: Unify toolchain, fix the three CI failures, and remove MSRV machinery [completed] + +- Component: `none` + +Apply every change in one commit (code fixes plus their tests, per repo convention that behavior tests ship with behavior changes): + +- `crates/gateway-stt/src/audio.rs:317`: remove unnecessary parens - `<(dyn std::error::Error + 'static)>::is::<...>` becomes `::is::<...>`. +- `crates/workshop/src/bridge.rs`: replace `#[expect(clippy::ptr_as_ptr, clippy::borrow_as_ptr)]` with `#[expect(clippy::ref_as_ptr)]` on the `#[implement(...)]` macro blocks. +- `crates/gateway-stt-engine/src/test_fixtures/tests/scenario_cleanup/decode.rs`: revert `decode_rendezvous_timeout` to real-time (remove `start_paused = true`). +- `rust-toolchain.toml`: `channel = "1.89"` becomes `channel = "stable"`. +- `Cargo.toml`: remove `rust-version = "1.89"` from `[workspace.package]`; remove `rust-version.workspace = true` from all 34 crate manifests under `crates/`. +- `.github/workflows/ci.yml`: remove global `env.RUSTUP_TOOLCHAIN: stable` (line 19) and per-job overrides (lines 37, 87, 136, 166, 238); delete the `msrv` job (lines 344-372); remove `msrv` from `ci-green`'s `needs:` list (line 394). +- `.github/workflows/stt-miri.yml`: remove `env.RUSTUP_TOOLCHAIN: 1.89` and `env.RUSTUP_AUTO_INSTALL: "0"` (lines 49-51) and the "Verify preinstalled MSRV Rust" step (lines 55-57); keep the `nightly-2026-09-05` Miri pin. +- `.github/workflows/release-workshop.yml`: remove the MSRV comment (lines 30-31) and `RUSTUP_TOOLCHAIN: stable` (line 32). +- `AGENTS.md`: remove the last bullet about CI linting on a newer stable than the pinned local MSRV. +- `README.md`: remove the "Minimum Rust Version" section (lines 112-114); change line 68 to "Every build needs Rust and Node.js 22"; remove or update the `rust-1.89+` badge (line 3). +- Delete `tools/validate-rust-1.89.0.ps1` and `tools/check-stt-native-workflow.test.mjs`. + + + + + +### Step 2: Run the full verification gate on stable and confirm green CI + +- Component: `none` + +Run once on the self-hosted runner: `rustup default stable`. Then locally on stable, in order: `cargo fmt --all --check`; `cargo clippy --workspace --all-targets --all-features -- -D warnings`; `cargo test --locked --workspace --all-features`; `cargo test --doc`; `RUSTDOCFLAGS="-D warnings" cargo doc --no-deps --all-features`. All must pass. Commit any residual fixes, push, and confirm the CI run on the same commit is green, with `ci-green` reflecting the remaining jobs after `msrv` removal. + + + + diff --git a/vibe/ACTIVE b/vibe/ACTIVE new file mode 100644 index 000000000..759c55f7a --- /dev/null +++ b/vibe/ACTIVE @@ -0,0 +1 @@ +vibe/2026-09-11-2-unify-toolchain.md \ No newline at end of file From 23ffa642fd69b46d4ff4f68b3d9978ba73d75ba5 Mon Sep 17 00:00:00 2001 From: Vinnie Falco Date: Fri, 11 Sep 2026 13:56:59 -0700 Subject: [PATCH 2/3] Rewrite timeout constants with from_mins and from_hours State the timeout durations in the units the values use. The change rewrites four timeout constants in `artifacts.rs`, `live.rs`, `backoff.rs`, and `cuda.rs` from `Duration::from_secs` products to `Duration::from_mins` and `Duration::from_hours`. - The rewritten constants keep their previous values; no test or call site changes. --- crates/gateway-local/src/artifacts.rs | 2 +- crates/gateway-local/src/chat_templates/live.rs | 2 +- crates/gateway/tests/it/cuda.rs | 6 +++--- crates/workshop-server/src/backoff.rs | 2 +- 4 files changed, 6 insertions(+), 6 deletions(-) diff --git a/crates/gateway-local/src/artifacts.rs b/crates/gateway-local/src/artifacts.rs index 2ea6d9512..a8e55655e 100644 --- a/crates/gateway-local/src/artifacts.rs +++ b/crates/gateway-local/src/artifacts.rs @@ -77,7 +77,7 @@ const DOWNLOAD_CONNECT_TIMEOUT: std::time::Duration = std::time::Duration::from_ /// accepts the connection and then sends nothing can never pin the /// provisioning thread forever - and a reader thread parked past the idle /// bound reaps when the ceiling drops its body. -const DOWNLOAD_REQUEST_TIMEOUT: std::time::Duration = std::time::Duration::from_secs(2 * 60 * 60); +const DOWNLOAD_REQUEST_TIMEOUT: std::time::Duration = std::time::Duration::from_hours(2); type Result = std::result::Result; diff --git a/crates/gateway-local/src/chat_templates/live.rs b/crates/gateway-local/src/chat_templates/live.rs index 334eec61e..ffb0e85ad 100644 --- a/crates/gateway-local/src/chat_templates/live.rs +++ b/crates/gateway-local/src/chat_templates/live.rs @@ -13,7 +13,7 @@ const LIVE_ENV: &str = "PROMPTFORGE_LIVE_CHAT_TEMPLATES"; const SERVER_ENV: &str = "PROMPTFORGE_LLAMA_SERVER"; const MODELS_ENV: &str = "PROMPTFORGE_CHAT_TEMPLATE_MODELS"; const API_KEY: &str = "promptforge-chat-template-live"; -const START_TIMEOUT: Duration = Duration::from_secs(20 * 60); +const START_TIMEOUT: Duration = Duration::from_mins(20); static LIVE_SERIAL: Mutex<()> = Mutex::new(()); #[derive(Debug, Deserialize)] diff --git a/crates/gateway/tests/it/cuda.rs b/crates/gateway/tests/it/cuda.rs index 538875d0b..84760afbd 100644 --- a/crates/gateway/tests/it/cuda.rs +++ b/crates/gateway/tests/it/cuda.rs @@ -36,12 +36,12 @@ const PROJECTOR_URL: &str = const PROJECTOR_SHA256: &str = "140be8d7849741f88c50757d529b84373ee8e27052cc2236855b537f4a8215fa"; /// First provisioning downloads the three pinned artifacts. -const PROVISION_TIMEOUT: Duration = Duration::from_secs(45 * 60); +const PROVISION_TIMEOUT: Duration = Duration::from_mins(45); /// A marker-hit relaunch skips downloads and re-hashing; only spawn and /// weight load remain. -const RELAUNCH_TIMEOUT: Duration = Duration::from_secs(15 * 60); +const RELAUNCH_TIMEOUT: Duration = Duration::from_mins(15); /// One completion against a warm server. -const COMPLETION_TIMEOUT: Duration = Duration::from_secs(5 * 60); +const COMPLETION_TIMEOUT: Duration = Duration::from_mins(5); /// Bound on waiting for the capture readers to drain the child's startup /// log: readiness is an HTTP probe, so it can beat the final piped bytes. const DIAGNOSTICS_TIMEOUT: Duration = Duration::from_secs(30); diff --git a/crates/workshop-server/src/backoff.rs b/crates/workshop-server/src/backoff.rs index 571838647..c8c902d5d 100644 --- a/crates/workshop-server/src/backoff.rs +++ b/crates/workshop-server/src/backoff.rs @@ -34,7 +34,7 @@ const MAX_DELAY: Duration = Duration::from_secs(60); /// rqbit. At the 60s ceiling this is more than a day of continuous /// outage; a workshop whose gateway has been gone that long stops /// probing and says so. -const TOTAL_DELAY_BUDGET: Duration = Duration::from_secs(24 * 60 * 60); +const TOTAL_DELAY_BUDGET: Duration = Duration::from_hours(24); /// Shared reconnect-backoff state; clones feed one schedule. /// From 11cbb65d1a0a88fe10182d60c974ba0ee45858a7 Mon Sep 17 00:00:00 2001 From: Vinnie Falco Date: Fri, 11 Sep 2026 14:35:21 -0700 Subject: [PATCH 3/3] Close plan: unify toolchain Plan: vibe/2026-09-11-2-unify-toolchain.md --- vibe-ledger.md | 2 ++ vibe/2026-09-11-2-unify-toolchain.md | 2 +- vibe/ACTIVE | 1 - 3 files changed, 3 insertions(+), 2 deletions(-) delete mode 100644 vibe/ACTIVE diff --git a/vibe-ledger.md b/vibe-ledger.md index 893e6a12c..d4994fceb 100644 --- a/vibe-ledger.md +++ b/vibe-ledger.md @@ -70,3 +70,5 @@ - Rulebook debt tiers Step 6: anyhow for test and build code, typed errors in remaining production - FULL gate pass: build, fmt, clippy (pinned and newer stable), docs, full workspace tests, workshop nextest, and doctests all green (verify-step-6-round-1.log); review clean including the cumulative component diff. Decision: build-ui uses anyhow, not a new thiserror type | Falsifier: the contract allows exactly one new public error type (FixtureError) and routes build tooling to anyhow. Decision: menu.rs gains private `SwitchFailure` with Display byte-identical to the old strings | Falsifier: no existing workshop-server enum models switch lifecycle. Decision: dialect.rs gains private `ToolCallRejection` with Display matching the old wire warnings exactly | Falsifier: the reasons become `gateway_warning` wire strings, so the text had to survive. Decision: confine.rs `parse_whoami_user_sid` returns `LocalError::CacheNotPrivate` directly | Falsifier: keeps the existing variant and message while dropping the String channel. Decision: the two LazyLock statics hold `SharedSource`, replayed via a new `Error::shared` helper | Falsifier: SharedSource exists precisely for re-producing typed errors from non-Clone caches. - Unify toolchain Step 1: Unify toolchain, fix the three CI failures, and remove MSRV machinery - focused verify pass: `cargo build` and `cargo nextest run -p gateway-stt-engine --all-features decode_rendezvous_timeout` green (verify-step-1-round-1.log); both clippy scopes and fmt clean during coding. Decision: rewrote the `rust-toolchain.toml` comment, which described the removed MSRV pin | Falsifier: the old comment claims a pin and CI overrides that no longer exist. Decision: left "Rust 1.89 or later" in five crate README.md files untouched (outside the step's enumerated files) | Falsifier: plan step or contract names them. Decision: split four out-of-scope `Duration::from_mins`/`from_hours` hunks into a separate commit (`c1ef217f`) rather than amending the step to name them | Falsifier: the plan file is treated as amendable after review, making the split commit redundant history. + +- Unify toolchain Step 2: Full verification gate and CI confirmation - local gate run and the fixes pushed to origin/master (`23ffa642`); the `native-whisper` self-hosted lane failed on provisioning (the runner's NetworkService rustup home had only 1.89), fixed by junctioning its `stable-x86_64-pc-windows-msvc` toolchain directory to the operator's profile copy with a read ACL, so the runner tracks the operator's stable from now on. The operator closed the run assuming the in-flight CI passes rather than watching it. Falsifier: the CI run on `23ffa642`. diff --git a/vibe/2026-09-11-2-unify-toolchain.md b/vibe/2026-09-11-2-unify-toolchain.md index 8e86917df..dea7f338d 100644 --- a/vibe/2026-09-11-2-unify-toolchain.md +++ b/vibe/2026-09-11-2-unify-toolchain.md @@ -146,7 +146,7 @@ Apply every change in one commit (code fixes plus their tests, per repo conventi -### Step 2: Run the full verification gate on stable and confirm green CI +### Step 2: Run the full verification gate on stable and confirm green CI [completed] - Component: `none` diff --git a/vibe/ACTIVE b/vibe/ACTIVE deleted file mode 100644 index 759c55f7a..000000000 --- a/vibe/ACTIVE +++ /dev/null @@ -1 +0,0 @@ -vibe/2026-09-11-2-unify-toolchain.md \ No newline at end of file