Skip to content
Merged
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
50 changes: 48 additions & 2 deletions .github/workflows/cef-learning-harness.yml
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,20 @@
# CEF-rendering regression introduced by an unrelated app-source PR is still
# caught promptly (informationally, non-blocking), not silently missed until
# someone next happens to touch a CEF-specific path.
#
# QNBS-v3: docs/cef/** deliberately excluded from the PR path list β€” a docs-only
# change (e.g. updating the competency matrix) can't regress the built host, so it
# doesn't need a ~30min rebuild; code changes under apps/desktop-cef/** or
# scripts/cef/** still trigger this regardless of whether docs are also touched.
# The push-to-main trigger stays deliberately unscoped (no `paths:`) precisely so an
# unrelated app-source change that alters dist/'s real inputs is still caught after
# merge β€” narrowing that trigger to apps/desktop-cef/** only would reintroduce the
# PR #388 gap this design already closed.
#
# QNBS-v3: `fast-gate` (lint+typecheck) runs before `harness` so a trivial code-style
# or type error fails in ~2-3min instead of after the full ~30min SDK+CMake+Rust+Xvfb
# cycle. `Swatinem/rust-cache` caches cargo's registry/git dirs for the rust-core
# crate Corrosion builds via CMake, cutting redundant crates.io re-fetches.
# ============================================================

name: πŸ§ͺ CEF Learning Harness
Expand All @@ -34,7 +48,6 @@ on:
paths:
- 'apps/desktop-cef/**'
- 'scripts/cef/**'
- 'docs/cef/**'
- '.github/workflows/cef-learning-harness.yml'
push:
branches: [main]
Expand All @@ -48,12 +61,31 @@ concurrency:
cancel-in-progress: ${{ github.event_name == 'pull_request' }}

jobs:
# QNBS-v3: fast, cheap checks (lint/typecheck) gate the ~30min CEF build so a trivial
# code-style or type error doesn't burn the full SDK-fetch+CMake+Rust+Xvfb cycle before
# surfacing. Duplicates a subset of ci.yml's quality job (which runs in parallel anyway) β€”
# deliberate trade: a couple extra lint/typecheck minutes vs. up to 30 wasted on a fail-fast case.
fast-gate:
name: ⚑ Fast gate (lint + typecheck)
runs-on: ubuntu-latest
timeout-minutes: 10
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
persist-credentials: false
- uses: ./.github/actions/setup
- name: Lint (Biome)
run: pnpm run lint
- name: Typecheck (tsgo)
run: npx tsgo --project tsconfig.tsgo.json --noEmit --checkers 4

harness:
name: πŸ§ͺ CEF host build, dependency inventory, launch-cycle proof
runs-on: ubuntu-latest
timeout-minutes: 30
needs: [fast-gate]
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v6
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
persist-credentials: false

Expand Down Expand Up @@ -86,6 +118,20 @@ jobs:
with:
toolchain: stable

# QNBS-v3: rust-core (apps/desktop-cef/rust-core) is built via CMake's corrosion_import_crate,
# invoking cargo under the hood β€” this caches the global registry/git dirs cargo uses regardless
# of where Corrosion places its target/ output, so repeat runs skip re-fetching crates.io deps.
- uses: Swatinem/rust-cache@6323deb102c322ba6fcbdcafc7e3dddab59af2b6 # v2.9.2
with:
workspaces: apps/desktop-cef/rust-core -> target

- name: Cache apt packages (CEF host + Wayland build deps)
uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0
with:
path: /var/cache/apt/archives/*.deb
Comment thread
qnbs marked this conversation as resolved.
# QNBS-v3: bump the -v suffix if either apt-get install package list in this job changes
key: apt-cef-harness-deps-v1

- name: Install worldscript_host build dependencies
run: |
sudo apt-get update
Expand Down
143 changes: 119 additions & 24 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ jobs:
pull-requests: read
steps:
# QNBS-v3: fetch-depth:0 gives gitleaks access to parent commits (sha^) needed for PR diff scans; shallow clone causes "ambiguous argument" errors.
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v6
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
fetch-depth: 0
persist-credentials: false
Expand Down Expand Up @@ -85,6 +85,43 @@ jobs:
if: github.event_name == 'pull_request'
uses: actions/dependency-review-action@a1d282b36b6f3519aa1f3fc636f609c47dddb294 # v5

# ----------------------------------------------------------
# 0b. CHANGE DETECTION: path-scopes rust-tauri so a docs/frontend-only PR
# doesn't pay for a Rust toolchain + apt-get(libgtk/libwebkit) build it can't
# affect. Fails OPEN (tauri=true) on any ambiguity β€” base SHA missing/unreachable
# β€” so a detection error runs the real gate instead of silently skipping it.
# ----------------------------------------------------------
changes:
name: πŸ“‚ Detect changed paths
runs-on: ubuntu-latest
timeout-minutes: 5
outputs:
tauri: ${{ steps.filter.outputs.tauri }}
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
fetch-depth: 0
persist-credentials: false

- name: Detect src-tauri changes
id: filter
run: |
if [ "${{ github.event_name }}" = "pull_request" ]; then
BASE="${{ github.event.pull_request.base.sha }}"
else
BASE="${{ github.event.before }}"
fi
if [ -z "$BASE" ] || [ "$BASE" = "0000000000000000000000000000000000000000" ] || ! git cat-file -e "$BASE" 2>/dev/null; then
echo "::notice::No usable base SHA to diff against β€” defaulting to tauri=true (fail open)"
echo "tauri=true" >> "$GITHUB_OUTPUT"
exit 0
fi
if git diff --name-only "$BASE" "${{ github.sha }}" | grep -qE '^(src-tauri/|\.github/workflows/ci\.yml$)'; then
echo "tauri=true" >> "$GITHUB_OUTPUT"
else
echo "tauri=false" >> "$GITHUB_OUTPUT"
fi

# ----------------------------------------------------------
# 1. QUALITY GATE: Lint + Typecheck + Tests (parallel matrix)
# ----------------------------------------------------------
Expand All @@ -101,7 +138,7 @@ jobs:
steps:
# QNBS-v3: fetch-tags β€” check-doc-metrics.mjs's stale-PLANNED-status check reads `git tag`
# and silently no-ops without one; a default shallow checkout has no tags at all.
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v6
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
persist-credentials: false
fetch-tags: true
Expand Down Expand Up @@ -177,24 +214,35 @@ jobs:
fail_ci_if_error: false

# ----------------------------------------------------------
# 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
# flaky, non-gating check. Mutation now runs ONLY via the manual `.github/workflows/
# mutation.yml` (workflow_dispatch). To be re-integrated in a later iteration.
# 1b. RUST-TAURI: Tauri Rust Gate (fmt/check/clippy/test), path-scoped via `changes`
# ----------------------------------------------------------
rust-tauri:
name: πŸ¦€ Tauri Rust Gate
runs-on: ubuntu-latest
timeout-minutes: 20
needs: [security]
needs: [security, changes]
# QNBS-v3: skips for PRs that don't touch src-tauri/** β€” ci-success treats 'skipped' as pass for this job only
if: needs.changes.outputs.tauri == 'true'
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v6
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
persist-credentials: false
- uses: dtolnay/rust-toolchain@29eef336d9b2848a0b548edc03f92a220660cdb8 # stable
with:
toolchain: stable
components: rustfmt, clippy
# QNBS-v3: no cargo caching existed before β€” fmt/check/clippy/test each rebuilt the full
# dependency tree from scratch every run. Keyed on Cargo.lock + rustc version, so a toolchain
# bump or lockfile change invalidates cleanly rather than reusing stale artifacts.
- uses: Swatinem/rust-cache@6323deb102c322ba6fcbdcafc7e3dddab59af2b6 # v2.9.2
with:
workspaces: src-tauri -> target
- name: Cache apt packages (Tauri Linux build deps)
uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0
with:
path: /var/cache/apt/archives/*.deb
Comment thread
qnbs marked this conversation as resolved.
# QNBS-v3: bump the -v suffix if the apt-get install package list below changes
key: apt-tauri-linux-deps-v1
- name: Install Linux Tauri build dependencies
run: |
sudo apt-get update
Expand All @@ -212,6 +260,12 @@ jobs:
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
# flaky, non-gating check. Mutation now runs ONLY via the manual `.github/workflows/
# mutation.yml` (workflow_dispatch). To be re-integrated in a later iteration.
# ----------------------------------------------------------
build:
name: πŸ—οΈ Build
runs-on: ubuntu-latest
Expand All @@ -223,7 +277,7 @@ jobs:
attestations: write
id-token: write
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v6
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
persist-credentials: false
- uses: ./.github/actions/setup
Expand All @@ -233,6 +287,16 @@ jobs:
env:
NODE_ENV: production

# QNBS-v3: caches the apt .deb archives `--with-deps` fetches (fonts, libx11, etc.) β€” shared
# key across every `playwright install --with-deps` call site in this workflow, since they all
# install the same system packages on the same runner image. actions/cache/save on a fresh
# write here also warms the cache for e2e/e2e-deep/storybook/vrt below.
- name: Cache apt packages (Playwright system deps)
uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0
with:
path: /var/cache/apt/archives/*.deb
key: apt-playwright-chromium-deps-v1

Comment thread
qnbs marked this conversation as resolved.
# QNBS-v3: The E2E suite runs `vite dev`, so prod-only rolldown bundling crashes (e.g. the
# zod DCE "init_locales is not defined" blank screen) never surface there. This guard loads
# the real production bundle in headless Chromium and fails if React does not mount.
Expand Down Expand Up @@ -290,33 +354,40 @@ jobs:
path: ./dist

# ----------------------------------------------------------
# 3. CI SUCCESS: single required-status aggregator (security + quality + build)
# 3. CI SUCCESS: single required-status aggregator (security + quality + changes + rust-tauri + build + e2e + vrt)
# ----------------------------------------------------------
ci-success:
name: βœ… CI Success
runs-on: ubuntu-latest
timeout-minutes: 5
needs: [security, quality, rust-tauri, build, e2e, vrt]
needs: [security, quality, changes, rust-tauri, build, e2e, vrt]
if: always()
steps:
# QNBS-v3: rust-tauri may legitimately be 'skipped' (changes.outputs.tauri == 'false') β€” that's a pass, not a failure
- name: Verify all required jobs succeeded
run: |
if [ "${{ needs.security.result }}" != "success" ] || \
[ "${{ needs.quality.result }}" != "success" ] || \
[ "${{ needs.rust-tauri.result }}" != "success" ] || \
[ "${{ needs.build.result }}" != "success" ] || \
[ "${{ needs.e2e.result }}" != "success" ] || \
[ "${{ needs.vrt.result }}" != "success" ]; then
FAIL=0
[ "${{ needs.security.result }}" = "success" ] || FAIL=1
[ "${{ needs.quality.result }}" = "success" ] || FAIL=1
[ "${{ needs.changes.result }}" = "success" ] || FAIL=1
if [ "${{ needs.rust-tauri.result }}" != "success" ] && [ "${{ needs.rust-tauri.result }}" != "skipped" ]; then
FAIL=1
fi
[ "${{ needs.build.result }}" = "success" ] || FAIL=1
[ "${{ needs.e2e.result }}" = "success" ] || FAIL=1
[ "${{ needs.vrt.result }}" = "success" ] || FAIL=1
if [ "$FAIL" = "1" ]; then
echo "One or more required jobs did not succeed:"
echo " security: ${{ needs.security.result }}"
echo " quality: ${{ needs.quality.result }}"
echo " rust-tauri: ${{ needs.rust-tauri.result }}"
echo " changes: ${{ needs.changes.result }}"
echo " rust-tauri: ${{ needs.rust-tauri.result }} (skipped = OK, src-tauri untouched)"
echo " build: ${{ needs.build.result }}"
echo " e2e: ${{ needs.e2e.result }}"
echo " vrt: ${{ needs.vrt.result }}"
exit 1
fi
echo "All required jobs succeeded."
echo "All required jobs succeeded (or were legitimately skipped)."

# ----------------------------------------------------------
# 4. DEPLOY: GitHub Pages (only on main push)
Expand Down Expand Up @@ -349,7 +420,7 @@ jobs:
timeout-minutes: 50
needs: [quality]
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v6
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
persist-credentials: false
- uses: ./.github/actions/setup
Expand All @@ -361,6 +432,12 @@ jobs:
path: ~/.cache/ms-playwright
key: playwright-${{ runner.os }}-${{ hashFiles('**/pnpm-lock.yaml') }}

- name: Cache apt packages (Playwright system deps)
uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0
with:
path: /var/cache/apt/archives/*.deb
key: apt-playwright-chromium-deps-v1

- name: Install Playwright browsers
run: pnpm exec playwright install --with-deps chromium

Expand Down Expand Up @@ -404,7 +481,7 @@ jobs:
needs: [quality]
continue-on-error: true
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v6
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
persist-credentials: false
- uses: ./.github/actions/setup
Expand All @@ -415,6 +492,12 @@ jobs:
path: ~/.cache/ms-playwright
key: playwright-${{ runner.os }}-${{ hashFiles('**/pnpm-lock.yaml') }}

- name: Cache apt packages (Playwright system deps)
uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0
with:
path: /var/cache/apt/archives/*.deb
key: apt-playwright-chromium-deps-v1

- name: Install Playwright browsers
run: pnpm exec playwright install --with-deps chromium

Expand Down Expand Up @@ -443,7 +526,7 @@ jobs:
needs: [build]
continue-on-error: false
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v6
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
persist-credentials: false
- uses: ./.github/actions/setup
Expand Down Expand Up @@ -490,7 +573,7 @@ jobs:
timeout-minutes: 25
needs: [quality]
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v6
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
persist-credentials: false
- uses: ./.github/actions/setup
Expand All @@ -506,6 +589,12 @@ jobs:
- name: Build Storybook
run: pnpm exec storybook build --output-dir storybook-static

- name: Cache apt packages (Playwright system deps)
uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0
with:
path: /var/cache/apt/archives/*.deb
key: apt-playwright-chromium-deps-v1

# QNBS-v3: Chromium-only; test-runner v0.24+ is async-compatible with Storybook v10.
- name: Install Playwright browsers (test-runner)
run: pnpm exec playwright install --with-deps chromium
Expand Down Expand Up @@ -557,7 +646,7 @@ jobs:
timeout-minutes: 15
needs: [build]
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v6
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
persist-credentials: false
- uses: ./.github/actions/setup
Expand All @@ -569,6 +658,12 @@ jobs:
name: dist
path: dist/

- name: Cache apt packages (Playwright system deps)
uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0
with:
path: /var/cache/apt/archives/*.deb
key: apt-playwright-chromium-deps-v1

- name: Install Playwright browsers
run: pnpm exec playwright install --with-deps chromium

Expand Down
14 changes: 12 additions & 2 deletions .gitleaks.toml
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,16 @@ useDefault = true
# broadening the exemption to "anything in that file" rather than narrowing it. The exact leaked
# value alone is already a highly-specific, non-generic string that cannot coincidentally match a
# real secret elsewhere, so it's the narrowest correct scope available in this schema.
#
# apt-cef-harness-deps-v1 / apt-tauri-linux-deps-v1
# (.github/workflows/cef-learning-harness.yml, ci.yml) are actions/cache `key:` values for apt
# archive caching β€” static, non-secret cache-namespace strings. Flagged by the same generic-api-key
# heuristic: YAML `key:` field name next to a hyphenated alphanumeric string. Reviewed 2026-08-19:
# both are cache keys with a fixed literal suffix (`-v1`), not credentials.
[allowlist]
description = "PASSPHRASE_SENTINEL_RECORD_KEY test mock β€” static IDB record-key identifier, not a credential"
regexes = ['''^idb_passphrase_sentinel_v1$''']
description = "Cache-key / record-key literals flagged by generic-api-key's key-adjacent-string heuristic β€” not credentials"
regexes = [
'''^idb_passphrase_sentinel_v1$''',
'''^apt-cef-harness-deps-v1$''',
'''^apt-tauri-linux-deps-v1$''',
]
Loading