Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions .github/dependabot.yml
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,15 @@ updates:
- 'wry'
- 'tao'

- package-ecosystem: cargo
directory: /src-tauri/fuzz
schedule:
interval: weekly
day: monday
cooldown:
default-days: 7
open-pull-requests-limit: 5

# GitHub Actions
- package-ecosystem: github-actions
directory: /
Expand Down
110 changes: 105 additions & 5 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,7 @@ jobs:
--config=src-tauri/osv-scanner.toml
--lockfile=pnpm-lock.yaml
--lockfile=src-tauri/Cargo.lock
--lockfile=src-tauri/fuzz/Cargo.lock

# QNBS-v3: gitleaks scans git history for secrets; GITHUB_TOKEN is enough for PR annotations.
- name: Scan for leaked secrets (gitleaks)
Expand All @@ -84,6 +85,8 @@ jobs:
- name: Dependency review (PRs only)
if: github.event_name == 'pull_request'
uses: actions/dependency-review-action@a1d282b36b6f3519aa1f3fc636f609c47dddb294 # v5
with:
allow-ghsas: GHSA-wrw7-89jp-8q8g

# ----------------------------------------------------------
# 1. QUALITY GATE: Lint + Typecheck + Tests (parallel matrix)
Expand Down Expand Up @@ -173,6 +176,101 @@ jobs:
flags: unit
fail_ci_if_error: false

# ----------------------------------------------------------
# 1a. RUST CHECK: native compile/lint/test gate for src-tauri/ (required — see ci-success)
# ----------------------------------------------------------
rust-check:
Comment on lines +180 to +182

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Update the feature-parity entry for the new Rust gate

Adding this required PR job makes docs/FEATURE-PARITY.md:54 incorrect: the enableRustCompute row still says native code is verified only through tauri-build.yml and explicitly claims there is no PR-CI gate. Contributors auditing feature coverage will therefore miss this new validation path; update that matrix alongside the other CI documentation changed in this commit.

Useful? React with 👍 / 👎.

name: 🦀 Rust Check
runs-on: ubuntu-latest
Comment thread
qnbs marked this conversation as resolved.
timeout-minutes: 20
needs: [security]
Comment thread
qodo-code-review[bot] marked this conversation as resolved.
if: always()
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v6
with:
fetch-depth: 0
persist-credentials: false
Comment thread
qodo-code-review[bot] marked this conversation as resolved.

- name: Detect src-tauri changes
id: filter
run: |
Comment thread
sourcery-ai[bot] marked this conversation as resolved.
if [ "${{ github.event_name }}" = "pull_request" ]; then
if git diff --quiet "${{ github.event.pull_request.base.sha }}" "${{ github.sha }}" -- src-tauri/ .github/workflows/ci.yml; then
echo "changed=false" >> "$GITHUB_OUTPUT"
Comment thread
qnbs marked this conversation as resolved.
else
echo "changed=true" >> "$GITHUB_OUTPUT"
fi
else
echo "changed=true" >> "$GITHUB_OUTPUT"
fi

- name: No src-tauri changes — skipping Rust checks
if: steps.filter.outputs.changed == 'false'
run: echo "src-tauri/ unchanged in this PR — nothing to compile."

- uses: dtolnay/rust-toolchain@29eef336d9b2848a0b548edc03f92a220660cdb8 # stable
if: steps.filter.outputs.changed == 'true'
with:
components: rustfmt, clippy

- uses: Swatinem/rust-cache@e18b497796c12c097a38f9edb9d0641fb99eee32 # v2
if: steps.filter.outputs.changed == 'true'
with:
workspaces: |
src-tauri
src-tauri/fuzz
cache-all-crates: true
prefix-key: "v1"

- name: Install Linux dependencies (WebKitGTK)
if: steps.filter.outputs.changed == 'true'
run: |
sudo apt-get update
sudo apt-get install -y libwebkit2gtk-4.1-dev libayatana-appindicator3-dev librsvg2-dev

- name: cargo fmt --check
if: steps.filter.outputs.changed == 'true'
working-directory: src-tauri
run: cargo fmt --check
Comment thread
qnbs marked this conversation as resolved.

- name: cargo check
if: steps.filter.outputs.changed == 'true'
working-directory: src-tauri
run: cargo check --locked
Comment thread
qnbs marked this conversation as resolved.
Comment thread
qnbs marked this conversation as resolved.

- name: cargo check (all features)
if: steps.filter.outputs.changed == 'true'
working-directory: src-tauri
run: cargo check --locked --all-targets --all-features

- name: cargo clippy
if: steps.filter.outputs.changed == 'true'
working-directory: src-tauri
run: cargo clippy --locked --all-targets -- -D warnings

- name: cargo clippy (all features)
if: steps.filter.outputs.changed == 'true'
working-directory: src-tauri
run: cargo clippy --locked --all-targets --all-features -- -D warnings

- name: cargo test
if: steps.filter.outputs.changed == 'true'
working-directory: src-tauri
run: cargo test --locked

- name: cargo fmt --check (fuzz crate)
if: steps.filter.outputs.changed == 'true'
run: cargo fmt --manifest-path src-tauri/fuzz/Cargo.toml -- --check

- name: cargo check (fuzz crate)
if: steps.filter.outputs.changed == 'true'
run: cargo check --locked --manifest-path src-tauri/fuzz/Cargo.toml
Comment thread
qnbs marked this conversation as resolved.
Comment thread
qnbs marked this conversation as resolved.

- name: cargo clippy (fuzz crate)
if: steps.filter.outputs.changed == 'true'
run: cargo clippy --locked --manifest-path src-tauri/fuzz/Cargo.toml -- -D warnings


# ----------------------------------------------------------
# 2. BUILD: Production build + Pages artifact upload
# QNBS-v3: Stryker mutation removed from the PR/CI pipeline (2026-06-02) — it added noise as a
Expand Down Expand Up @@ -257,24 +355,26 @@ jobs:
path: ./dist

# ----------------------------------------------------------
# 3. CI SUCCESS: single required-status aggregator (security + quality + build)
# 3. CI SUCCESS: single required-status aggregator (security + quality + rust-check + build)
# ----------------------------------------------------------
ci-success:
name: ✅ CI Success
runs-on: ubuntu-latest
timeout-minutes: 5
needs: [security, quality, build]
needs: [security, quality, rust-check, build]
if: always()
steps:
- name: Verify all required jobs succeeded
run: |
if [ "${{ needs.security.result }}" != "success" ] || \
[ "${{ needs.quality.result }}" != "success" ] || \
[ "${{ needs.rust-check.result }}" != "success" ] || \
[ "${{ needs.build.result }}" != "success" ]; then
echo "One or more required jobs did not succeed:"
echo " security: ${{ needs.security.result }}"
echo " quality: ${{ needs.quality.result }}"
echo " build: ${{ needs.build.result }}"
echo " security: ${{ needs.security.result }}"
echo " quality: ${{ needs.quality.result }}"
echo " rust-check: ${{ needs.rust-check.result }}"
echo " build: ${{ needs.build.result }}"
exit 1
fi
echo "All required jobs succeeded."
Expand Down
2 changes: 1 addition & 1 deletion AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -342,7 +342,7 @@ deploy (main, non-PR) needs: build + e2e ──► GitHub Pages
- `tauri-build.yml` runs on `workflow_dispatch` or `v*` tags. `v*` tags publish installers on a GitHub Release.
- Artifacts: `.appimage`, `.msi`, `.dmg` + `latest.json` updater manifest.
- Signing: Optional `TAURI_SIGNING_PRIVATE_KEY` and password for updater signatures.
- **No PR-CI gate for Rust:** the web `ci.yml` never compiles `src-tauri/`, and the crate may not build on constrained hardware. After **any** `src-tauri/` change, verify by dispatching the build on your branch — `gh workflow run tauri-build.yml --ref <branch>` — and confirm it reaches `Finished N bundles`. ubuntu/macOS are the meaningful Rust signal. See [`docs/TAURI-CI.md`](docs/TAURI-CI.md) § *Verifying native (Rust) changes*.
- **PR-CI Rust gate (`rust-check` in `ci.yml`, required):** `cargo fmt --check` / `cargo check --locked` / `cargo clippy --locked --all-targets -- -D warnings` / `cargo test --locked` run on every PR that touches `src-tauri/` or `.github/workflows/ci.yml` itself; other PRs skip the cargo steps via an in-job change-detection filter (the job still runs so it's a valid `needs:` dependency for `ci-success`). This only proves the crate **compiles, lints clean, and unit-tests pass on ubuntu-latest** — it does not build/sign real installers or verify macOS/Windows-specific code paths. After **any** `src-tauri/` change that touches platform-specific code, or before a release, still verify by dispatching the full bundle build on your branch — `gh workflow run tauri-build.yml --ref <branch>` — and confirm it reaches `Finished N bundles`. See [`docs/TAURI-CI.md`](docs/TAURI-CI.md) § *Verifying native (Rust) changes*.

### Deployment Targets

Expand Down
54 changes: 54 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,60 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

## [Unreleased]

### Added

- **Required Rust/Tauri compile gate on pull requests** (`rust-check` job,
`.github/workflows/ci.yml`). Previously the only Rust CI was an advisory-only OSV vulnerability scan plus a full
installer build on tag-push/manual-dispatch only (`tauri-build.yml`) — native code could merge
without ever having compiled. The new job runs `cargo fmt --check` / `cargo check --locked` /
`cargo clippy --locked --all-targets -- -D warnings` / `cargo test --locked` on every PR that
touches `src-tauri/` (or the job's own definition in `ci.yml`); other PRs skip the cargo steps
via an in-job change-detection filter, so the job always runs (a valid `needs:` dependency for
`ci-success`) at negligible cost when nothing Rust-related changed. **Review-loop follow-up
fixes to the same change:** the crate was not actually `rustfmt`-compliant (no `rustfmt.toml`,
pre-existing 2-space indentation vs. the 4-space default) — the first real PR to touch
`src-tauri/` would have failed `cargo fmt --check` immediately; reformatted the whole crate to
the rustfmt default instead of pinning the check to non-standard style. The change-detector only
diffed `src-tauri/`, so edits to the Rust job's own logic in `ci.yml` — including this PR's
original commit — never actually exercised the cargo steps; now also diffs `ci.yml` itself.
`src-tauri/fuzz/` (a separate nested crate, `cargo-fuzz`) was never checked and its path
dependency on the parent crate was actually broken (`[dependencies.app_lib] path = ".."` with no
`package =` override — the parent package is named `worldscript-studio`, so cargo could never
resolve it); fixed the dependency declaration, generated and committed its `Cargo.lock`, and
added an explicit `cargo check` step for it. Added `if: always()` at the job level so it still
runs (and reports real Rust diagnostics) even when the unrelated `security` job fails, matching
the job's own "always runs" framing. `AGENTS.md` § Desktop Releases and `docs/TAURI-CI.md` still
said "no PR-CI gate for Rust" / "never compiles `src-tauri/`" — updated both to describe what
the new gate covers and when the full cross-platform `tauri-build.yml` dispatch remains
necessary (macOS/Windows-specific code, real installer signing). Removed several newly-added
inline YAML rationale comments that violated this repo's own "no inline comments in config
files, explain in the commit message" rule. **Third round of review-loop follow-up:** once the
fuzz crate's dependency was actually fixed and its `Cargo.lock` committed, the pre-existing
`security` job's `dependency-review-action` step started failing on `glib@0.18.5`
(`GHSA-wrw7-89jp-8q8g`) — a transitive dependency pulled in via the fuzz crate's path dependency
on the parent app. This is the same already-documented, already-accepted risk in
`src-tauri/osv-scanner.toml` (`RUSTSEC-2024-0429` — no fix available without a `webkit2gtk` 4.1+
upgrade Tauri 2.x doesn't yet support); `dependency-review-action` doesn't read that file, so it
re-flagged the same risk fresh. Added a matching `allow-ghsas` entry so the two tools' accepted
lists agree instead of one silently re-litigating the other's documented exception. **Fourth
round of review-loop follow-up:** the fuzz-crate check only ran `cargo check`, not `cargo fmt
--check`/`cargo clippy` — added both (the crate wasn't actually formatted; fixed). The OSV
scanner's `scan-args` never listed `src-tauri/fuzz/Cargo.lock`, so vulnerabilities in the fuzz
dependency graph were invisible to the enforced security gate — added. `src-tauri/fuzz/Cargo.lock`
is format v4 (stabilized in Cargo 1.83), incompatible with the parent crate's declared 1.77.2
MSRV; declared a separate `rust-version = "1.83"` for the fuzz crate (dev-only tooling, never
shipped) so an older toolchain fails with a clear error instead of a confusing lockfile-parse
Comment on lines +50 to +52

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Record the fuzz crate's actual minimum Rust version

The changelog says the fuzz crate now declares rust-version = "1.83", but the committed src-tauri/fuzz/Cargo.toml declares 1.89 because its locked dependencies require that newer toolchain. Anyone relying on these release notes to reproduce the fuzz checks with Rust 1.83 will still fail before compilation, so update this entry to record the final 1.89 requirement.

Useful? React with 👍 / 👎.

failure. `.github/dependabot.yml` only watched `/src-tauri`, so the fuzz crate's independent
dependency graph never got automated update PRs — added a second Cargo entry for
`/src-tauri/fuzz`. `Swatinem/rust-cache`'s `workspaces` only listed `src-tauri`, so every run
recompiled the fuzz crate's entire dependency tree (the whole Tauri stack, via its path
dependency on the parent) uncached — added `src-tauri/fuzz` to the cached workspaces and gave
the job a larger timeout margin. Separately, found (via the same review pass) and fixed a
real, unrelated functional gap: `src-tauri/capabilities/default.json`'s `http:default` allowlist
was missing `https://api.anthropic.com/*`, even though desktop is documented to call Anthropic
directly via native HTTP — the CSP already permitted the origin, but the Tauri HTTP-plugin
capability didn't, so every desktop Anthropic API call would have been denied.

## [1.27.0] — 2026-08-13

### Added
Expand Down
23 changes: 10 additions & 13 deletions docs/CI.md
Original file line number Diff line number Diff line change
Expand Up @@ -72,12 +72,14 @@ Each job that uses the composite must call `actions/checkout@v6` first (local co

```text
security ──► quality ──┬──► build ──┬──► lighthouse
├──► e2e └──► vrt
└──► storybook
│ ├──► e2e └──► vrt
│ └──► storybook
└──► rust-check (cargo steps run when src-tauri/ or ci.yml itself changes)

security ─┬
quality ──┼──► ci-success (required-status aggregator)
build ────┘
security ─────┬
quality ──────┼
rust-check ───┼──► ci-success (required-status aggregator)
build ────────┘

build (main, non-PR) ──► upload-pages-artifact
deploy (main, non-PR) needs: build + e2e ──► GitHub Pages
Expand All @@ -89,21 +91,16 @@ Mutation testing (Stryker) is **not** in this graph — it runs only via manual
|-----|--------|---------|
| `security` | — | `pnpm audit --audit-level=high`; **OSV scanner** (`google/osv-scanner-action`) for npm + Rust lockfiles; `gitleaks` secrets scan; on PRs: `dependency-review-action` |
| `quality` | `security` | Matrix **Node 22** and **24** → Biome lint, **`pnpm run i18n:check`**, **`pnpm run docs:check`**, **`pnpm run parity:check`**, `pnpm run typecheck`, Vitest + coverage (+ non-blocking coverage-ratchet suggestion), Codecov (optional token), coverage artifact |
| `rust-check` | `security` | `cargo fmt --check`, `cargo check --locked`, `cargo clippy --locked --all-targets -- -D warnings`, `cargo test --locked` against `src-tauri/`; the nested non-workspace `src-tauri/fuzz/` manifest receives `cargo fmt --check`, `cargo check --locked`, and Clippy with warnings denied. The job always runs (valid `ci-success` dependency), but skips all cargo steps entirely when the PR diff touches neither `src-tauri/**` nor `.github/workflows/ci.yml` itself (an edit to the job's own logic must exercise it too), so frontend-only PRs pay no meaningful cost. Before this job, native code could merge with zero compile/lint/test verification — only an advisory OSV vuln scan (in `security`, above) ever touched `Cargo.lock`, and the only workflow that actually ran `cargo build` was tag-push/manual-dispatch-only ([`tauri-build.yml`](../.github/workflows/tauri-build.yml)), never PRs. |
| `build` | `quality` | Production `pnpm run build`, **`bundle:budget`**, **`analyze`** (upload `bundle-analysis.html`), **`pnpm run smoke:prod`** (headless-Chromium prod-build + CSP-runtime gate — see below), `dist` artifact; on `main` (non-PR): Pages artifact + **SLSA build provenance attestation**. No `if:` on the job itself — `smoke:prod` runs on every PR, not just `main` pushes. |
| `e2e` | `quality` | Playwright **Chromium** + **Mobile Chrome** (Pixel 5) — `CI=true`, 2× retries, 50 min timeout; browser cache via `actions/cache@v5`. Firefox optional locally. `PLAYWRIGHT_SKIP_VRT=true` (VRT is its own job). |
| `lighthouse` | `build` | LHCI (mobile): **accessibility error gate** `minScore: 0.95`; **CLS error** ≤ 0.1; performance/SEO warn. Desktop run: `continue-on-error: true` until baselines stabilise. Timeout 25 min. |
| `storybook` | `quality` | Cloud-first — Storybook build + test-runner only run in CI (not locally); Playwright browser cache `v5`; `--maxWorkers=2 --junit` (non-blocking, `continue-on-error: true` — see [exit criteria](#non-blocking-gates--exit-criteria-f-13)); artifacts uploaded always. Debug: manual `storybook-debug.yml` workflow. |
| `vrt` | `build` | Visual regression against production `dist`; `toHaveScreenshot()` with committed PNG baselines (4 views × Chromium); artifacts uploaded always |
| `ci-success` | `security`, `quality`, `build` | Required-status **aggregator** — `if: always()`, fails if any of its three `needs` didn't resolve to `success` (a matrix job like `quality` only reports `success` once every Node 22/24 leg passes). Exists so branch protection can require **one** context instead of enumerating `security`/`quality (Node 22)`/`quality (Node 24)`/`build` by name; a future required job just joins this job's `needs` list, with no branch-protection settings edit needed. |
| `ci-success` | `security`, `quality`, `rust-check`, `build` | Required-status **aggregator** — `if: always()`, fails if any of its `needs` didn't resolve to `success` (a matrix job like `quality` only reports `success` once every Node 22/24 leg passes). Branch protection requires **only** this one context (`✅ CI Success`) — confirmed live via `gh api repos/qnbs/WorldScript-Studio/branches/main/protection`, not the 4 individual contexts. A future required job just joins this job's `needs` list, with no branch-protection settings edit needed — exactly how `rust-check` was added. |
| `deploy` | `build`, `e2e` | **Only** `main` push (not PR): `deploy-pages` |

> **Desktop:** On-demand / tag-driven Tauri bundles live in [`tauri-build.yml`](../.github/workflows/tauri-build.yml); **`v*` tags** additionally publish installers on a **GitHub Release**. See [`docs/TAURI-CI.md`](TAURI-CI.md). Desktop CI does not block the web deploy graph above.
>
> **Maintainer follow-up (not done here):** switching branch protection's required-checks list from the
> 4 individual contexts to just `ci-success` is a branch-protection settings change — out of scope for
> an automated PR. Do it manually once `ci-success` has run green on `main` at least once: repo
> Settings → Branches → `main` → Required status checks → remove the 4 individual entries, add
> `✅ CI Success`.

---

Expand Down Expand Up @@ -195,7 +192,7 @@ once a manual run demonstrates the flakiness is resolved — concretely, three c
| **Dependabot** | Weekly (Monday) | PRs for npm deps (dev-tooling grouped) + GitHub Actions SHA bumps (max 5 open PRs) |
| **`dependency-review-action`** | PRs only (security job) | Blocks PRs that introduce new high/critical vulnerabilities |
| **pnpm v11 build-script policy** | Dependency installation | `pnpm-workspace.yaml` uses the sole supported, default-deny `allowBuilds` map; legacy build-script lists are intentionally absent |
| **Branch protection** | Always | `main` requires 1 approved review, required status checks (security, quality ×2, build), no force-push |
| **Branch protection** | Always | `main` requires 1 approved review, required status check (`✅ CI Success` aggregator only — see `ci-success` row above), no force-push |

Only the two reviewed native packages marked `true` in `allowBuilds` (`@swc/core` and `esbuild`)
may run dependency lifecycle scripts. `@google/genai`, `core-js`, `onnxruntime-node`, `protobufjs`,
Expand Down
Loading
Loading