---
name: CI pipeline
type: architecture-map-page
slice: 05-core-storage-hotkey-build
last_verified: 2026/05/09
---

# CI pipeline

> **Where you are:** [Architecture map](../README.md) → [Core, Storage, Hotkey, Build](README.md) → CI pipeline

**Plain English summary.** Three GitHub Actions workflows. `check.yml` runs per-push compile + lint + library tests + frontend build on Linux, Windows, and macOS. `build.yml` produces installer artefacts (.AppImage/.deb on Linux, .msi/.exe on Windows, .dmg/.app on macOS) for tag pushes and on-demand. `audit.yml` runs cargo-audit and npm-audit weekly.

## At a glance

- Files: `.github/workflows/check.yml`, `build.yml`, `audit.yml`.
- LOC: 7,017 + 7,157 + 1,443 bytes respectively.
- Shared concerns across check and build: same Linux / macOS / Windows system-package install steps (libwebkit2gtk, Vulkan SDK, libclang for bindgen), same `Swatinem/rust-cache@v2` cache scoping (`workspaces: .` because the workspace target dir is `./target`, **not `src-tauri/target`** — this was the silent miss that made Windows checks slow).

## `check.yml` — per-push compile + lint + tests

### Triggers

```yaml
on: [push, pull_request]
concurrency:
  group: check-${{ github.ref }}
  cancel-in-progress: true   # newer push supersedes older
```

### Jobs

#### `rust` — matrix

```yaml
strategy:
  fail-fast: false
  matrix:
    os: [ubuntu-22.04, windows-latest, macos-latest]
```

Steps per OS:

1. **Install system deps** — libwebkit2gtk-4.1, libappindicator3, librsvg2, libasound2, libudev, patchelf, cmake, build-essential, libclang, clang, libvulkan, glslang-tools, spirv-tools (Linux); Homebrew vulkan-headers, vulkan-loader, molten-vk, shaderc, llvm (macOS); choco llvm + vulkan-sdk (Windows). Each install resolves `LIBCLANG_PATH` / `VULKAN_SDK` dynamically so a minor SDK version bump does not hardcode-break the step.
2. `dtolnay/rust-toolchain@stable` with `rustfmt, clippy`.
3. `Swatinem/rust-cache@v2` with `workspaces: .` and shared key `lumotia-${{ matrix.os }}`.
4. `cargo check --workspace --all-targets`.
5. `cargo fmt --all -- --check`.
6. `cargo clippy --workspace --all-targets -- -D warnings`.
7. `cargo test --workspace --lib` (Linux only, gated on `matrix.os == 'ubuntu-22.04'`).
8. `cargo audit` (Linux only).

The Linux-only gating on tests + audit keeps macOS and Windows legs focused on compile coverage. The library-tests-only flag (`--lib`) excludes integration tests that need a runtime / GPU.

#### `frontend` — Linux only

1. Setup Node 20.
2. `npm ci`.
3. `npm audit --audit-level=high`.
4. `npm run build` — Vite-only build (no `tauri build`).
5. `svelte-check` for type and template errors.

### Why the workspace cache scope matters

The original CI step pointed `Swatinem/rust-cache@v2` at `workspaces: src-tauri`. The workspace target dir is at `./target` (defined by the repo-root `Cargo.toml`), not `src-tauri/target`. That meant the cache silently missed every run, and Windows check runs felt like they recompiled `sqlx` from scratch every time. Documented inline at `check.yml`. The current scope `workspaces: .` is the fix.

## `build.yml` — release artefacts

### Triggers

```yaml
on:
  push:
    tags: ['v*']             # tagged releases (v0.1.0, v0.2.0, ...)
  workflow_dispatch:          # manual, any branch
    inputs:
      tag_name:
        description: 'Optional tag name to attach the build to'
        required: false
```

`workflow_dispatch` lets us build a Windows binary on demand to dual-boot test without cutting a release.

### Concurrency

```yaml
concurrency:
  group: build-${{ github.ref }}
  cancel-in-progress: false   # release builds finish even on tag re-pushes
```

Different from `check.yml`: a release build is too expensive to abandon mid-flight.

### Matrix

```yaml
include:
  - os: ubuntu-22.04   artefacts: *.AppImage, *.deb
  - os: windows-latest artefacts: *.msi,      *.exe
  - os: macos-latest   artefacts: *.dmg,      *.app
```

### Steps

1. System deps (same as `check.yml`).
2. Rust toolchain.
3. Cache (`shared-key: lumotia-build-${{ matrix.os }}`, distinct from check.yml's key so the build cache is not invalidated by check runs).
4. `npm ci`.
5. `tauri-apps/tauri-action@v0` — runs `tauri build`. On tag pushes, attaches artefacts to a draft GitHub Release. Empty `tagName` for `workflow_dispatch` so we get artefacts only.
6. `actions/upload-artifact@v4` — always uploads to the run page (30-day retention) so the workflow_dispatch path produces something downloadable too.
7. `du -h` on the produced bundle directory for visibility.

### Code signing

**Not configured.** Both macOS (Apple Developer ID) and Windows (code-signing certificate) signing blocks are commented out in `build.yml`. Users hit Gatekeeper / SmartScreen on first run.

To enable later:

- **macOS:** uncomment the `APPLE_SIGNING_IDENTITY` / `APPLE_CERTIFICATE` / `APPLE_CERTIFICATE_PASSWORD` env block; add the corresponding repo secrets.
- **Windows:** uncomment the `WINDOWS_CERTIFICATE` / `WINDOWS_CERTIFICATE_PASSWORD` env block; add the secrets.

Documented in the file header.

## `audit.yml` — weekly vulnerability scan

### Triggers

```yaml
on:
  schedule:
    - cron: "0 6 * * 1"    # Mondays 06:00 UTC
  workflow_dispatch:
```

Mondays so any advisory has the whole week to be triaged rather than landing on a Friday.

### Jobs

#### `cargo-audit`

Uses `rustsec/audit-check@v2` against the RustSec advisory DB. Fails on any unignored advisory.

#### `npm-audit`

`npm audit --audit-level=high`. Ignores low / moderate noise — only high and critical advisories warrant a bump.

### Why a separate workflow?

A newly published advisory surfaces as its own failing run (easy to spot, easy to track) without blocking unrelated PR work. The same `cargo audit` runs as part of `check.yml` on every push, so this workflow is the catch-all for advisories that land between pushes.

## Watch-outs

- **`cargo audit` runs in two places.** `check.yml` (Linux only, on every push) and `audit.yml` (weekly). The duplication is cheap and useful.
- **`tauri-action@v0` is unpinned.** `@v0` floats. A breaking change in tauri-action would surface on the next release attempt. Worth pinning to a specific version when v0.1 ships.
- **macOS `LIBCLANG_PATH` resolution.** `brew --prefix llvm` resolves to the Apple Silicon path on M-series runners (`/opt/homebrew/...`) and the Intel path on intel runners (`/usr/local/...`). The dynamic resolution handles both.
- **Linux runners use Ubuntu 22.04, not 24.04.** Pinned because 22.04's `libwebkit2gtk-4.1` package is stable; 24.04's namespace shifted. Worth re-evaluating once GitHub Actions retires 22.04.
- **No nightly clippy.** The `-D warnings` clippy step uses stable. A nightly canary job would catch upcoming lint changes earlier.

## See also

- [Workspace Cargo.toml](workspace-cargo.md) — the release profile this CI consumes.
- [Dev launcher and scripts](dev-launcher-and-scripts.md) — local equivalent.
