Skip to content
Closed
Show file tree
Hide file tree
Changes from 1 commit
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
90 changes: 85 additions & 5 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -173,6 +173,84 @@ jobs:
flags: unit
fail_ci_if_error: false

# ----------------------------------------------------------
# 1a. RUST CHECK: native compile/lint/test gate for src-tauri/ (required — see ci-success)
# QNBS-v3: previously the ONLY Rust CI was an OSV vuln scan (security job, advisory-only) plus
# a full bundle build on tag-push/manual-dispatch only (tauri-build.yml) — native code
# could merge without ever having compiled. This job always runs (so it's a valid
# `needs:` dependency for ci-success) but skips the actual cargo steps when the diff
# doesn't touch src-tauri/, so frontend-only PRs pay no meaningful cost.
Comment thread
qnbs marked this conversation as resolved.
Outdated
# ----------------------------------------------------------
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: 15
needs: [security]
Comment thread
qodo-code-review[bot] marked this conversation as resolved.
steps:
# QNBS-v3: fetch-depth:0 so the base-branch diff below can always resolve base.sha (same
# reasoning as the security job's gitleaks checkout).
- 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/; then
echo "changed=false" >> "$GITHUB_OUTPUT"
Comment thread
qnbs marked this conversation as resolved.
else
echo "changed=true" >> "$GITHUB_OUTPUT"
fi
else
# Push to main / workflow_dispatch: always verify — merges are infrequent, cost is trivial.
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
cache-all-crates: true
# QNBS-v3: same prefix-key scheme as tauri-build.yml — bump together on Rust channel/Cargo.lock churn.
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 clippy
if: steps.filter.outputs.changed == 'true'
working-directory: src-tauri
run: cargo clippy --locked --all-targets -- -D warnings

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

# ----------------------------------------------------------
# 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 +335,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
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 (path-scoped: src-tauri/ only)
Comment thread
coderabbitai[bot] marked this conversation as resolved.
Outdated

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/`. Path-scoped: the job always runs (valid `ci-success` dependency), but skips the cargo steps entirely when the PR diff doesn't touch `src-tauri/**`, so frontend-only PRs pay no meaningful cost. Prior to 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