Repository navigation
test(repeat): blob_hash vectors at SHA-1 padding boundaries #102
Workflow file for this run
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| name: CI | |
| on: | |
| push: | |
| branches: [main] | |
| pull_request: | |
| branches: [main] | |
| # Cancel in-progress runs on the same branch so a fast follow-up commit | |
| # doesn't queue behind a stale one. | |
| concurrency: | |
| group: ci-${{ github.ref }} | |
| cancel-in-progress: true | |
| env: | |
| PNPM_VERSION: '10' | |
| NODE_VERSION: '24' | |
| jobs: | |
| check: | |
| name: Check (macOS Apple Silicon) | |
| runs-on: macos-14 | |
| steps: | |
| # ── Checkout + toolchains ───────────────────────────────────────────── | |
| - uses: actions/checkout@v7 | |
| - uses: dtolnay/rust-toolchain@stable | |
| with: | |
| components: rustfmt, clippy | |
| - uses: swatinem/rust-cache@v2 | |
| with: | |
| workspaces: ". -> target" | |
| cache-on-failure: true | |
| - uses: actions/setup-node@v7 | |
| with: | |
| node-version: ${{ env.NODE_VERSION }} | |
| - uses: pnpm/action-setup@v6 | |
| with: | |
| version: ${{ env.PNPM_VERSION }} | |
| # ── Rust checks ─────────────────────────────────────────────────────── | |
| - name: cargo fmt | |
| run: cargo fmt --all --check | |
| - name: cargo clippy | |
| # Exclude the Tauri app: its build script requires sidecar binaries | |
| # that only exist after `build-sidecar.sh` runs (release pipeline only). | |
| run: cargo clippy --workspace --all-targets --exclude Lumen -- -D warnings | |
| # Every non-Tauri crate, not just lumen-core: lumen-cli and lumen-stats | |
| # were never tested on any platform, and lumen-stats holds the rollup | |
| # arithmetic the GUI displays. | |
| - name: cargo test (all non-Tauri crates) | |
| run: cargo test -p lumen-core -p lumen-cli -p lumen-daemon -p lumen-mcp -p lumen-stats --all-targets | |
| # ── Frontend ───────────────────────────────────────────────────────── | |
| # Verifies the Angular build compiles clean; catches template errors | |
| # and TypeScript issues before they block a release. | |
| - name: pnpm install | |
| working-directory: lumenator | |
| run: pnpm install --frozen-lockfile | |
| - name: pnpm build | |
| working-directory: lumenator | |
| run: pnpm build | |
| # The frontend suite enforces its own coverage thresholds via the | |
| # `test` target in angular.json, per file: a file under any of them fails | |
| # here by name, however high the total stays. Every file under src/app is | |
| # measured whether a spec loads it or not, except what coverageExclude lists: | |
| # src/main.ts the bootstrap call, which runs only in a window | |
| # src/**/*.spec.ts the specs | |
| # src/app/tauri-bridge.fake.ts the test double the specs drive the app through | |
| # src/app/tauri-bridge.ts one-line calls into @tauri-apps/api, which | |
| # throws outside a Tauri window; the App e2e job | |
| # runs it, in the built app | |
| - name: pnpm test | |
| working-directory: lumenator | |
| run: pnpm test | |
| # ── End-to-end gate ───────────────────────────────────────────────────────── | |
| # | |
| # A separate job so it is a named status check that branch protection can require. | |
| # These tests also run inside `cargo test --all-targets` in `check` and `test`, but a | |
| # protection rule can require a job, not a subset of one — and the point of this gate | |
| # is that "the chain from a file on disk to the figure on screen still works" cannot be | |
| # merged past. | |
| # | |
| # Two chains, both driving real binaries with no stubs between the ends: | |
| # read path — a source file -> the lumen-mcp binary over stdio -> SQLite -> | |
| # the Optimizer and Hotspots aggregates the GUI renders | |
| # ingest path — a Claude Code transcript line -> the lumen-daemon binary -> | |
| # SQLite + a live WebSocket frame -> the same aggregates | |
| # | |
| # Ubuntu rather than macOS: it is the cheapest runner, and nothing in either chain is | |
| # platform-specific. The per-OS `test` job below proves the code compiles and passes | |
| # everywhere. | |
| e2e: | |
| name: E2E (read path + ingest path) | |
| runs-on: ubuntu-22.04 | |
| steps: | |
| - uses: actions/checkout@v7 | |
| - uses: dtolnay/rust-toolchain@stable | |
| - uses: swatinem/rust-cache@v2 | |
| with: | |
| workspaces: ". -> target" | |
| cache-on-failure: true | |
| # The read-path test spawns the real lumen-mcp binary; the ingest-path test spawns | |
| # the real lumen-daemon. Building them first makes a compile failure read as a | |
| # build error rather than as a mysterious spawn failure inside a test. | |
| - name: Build the binaries under test | |
| run: cargo build -p lumen-mcp -p lumen-daemon | |
| - name: E2E — read path (file -> MCP binary -> ledger -> screen) | |
| run: cargo test -p lumen-mcp --test e2e_read_to_screen -- --nocapture | |
| - name: E2E — ingest path (transcript -> daemon -> ledger + websocket) | |
| run: cargo test -p lumen-daemon --test e2e_pipeline -- --nocapture | |
| # The metering contract the A/B depends on: every decision column written, a | |
| # decline recorded on its own route, the flag off by default. | |
| - name: E2E — decision metering | |
| run: cargo test -p lumen-mcp --test ranked_metering -- --nocapture | |
| # ── Every crate, every target, on all three: macOS, Linux, Windows ─────────── | |
| # The same tests everywhere, the Tauri crate included: its build needs the sidecars, | |
| # so they are built first, and on Linux the GTK/WebKit headers. Before anything is | |
| # built, the probe prints what a hook's shell finds on the runner, unedited. | |
| # | |
| # The hook end-to-end tests run on their own first, with their output shown: each | |
| # installs with Setup into a temp home, hands a payload Claude Code really sent to the | |
| # registered command in the shell Claude Code uses there (/bin/sh on macOS and Linux, | |
| # Git Bash on Windows), and prints the row that landed. They run again in the full | |
| # suite; this step is where to read them. | |
| test: | |
| name: Test (${{ matrix.os }}) | |
| strategy: | |
| fail-fast: false | |
| matrix: | |
| os: [ubuntu-latest, windows-latest, macos-14] | |
| runs-on: ${{ matrix.os }} | |
| # A warning fails the build here as clippy fails it in `check`, which runs on macOS only: | |
| # code compiled out on macOS warned on Linux and Windows, unread, until 1.6.0. | |
| env: | |
| RUSTFLAGS: -D warnings | |
| defaults: | |
| run: | |
| # Git Bash on Windows: the shell Claude Code runs hooks with there. | |
| shell: bash | |
| steps: | |
| - uses: actions/checkout@v7 | |
| - name: Probe the hook environment | |
| run: | | |
| if [ "$RUNNER_OS" = Windows ]; then | |
| bash scripts/probe-hook-env.sh | |
| else | |
| /bin/sh scripts/probe-hook-env.sh | |
| fi | |
| - uses: dtolnay/rust-toolchain@stable | |
| - uses: swatinem/rust-cache@v2 | |
| with: | |
| workspaces: ". -> target" | |
| cache-on-failure: true | |
| - name: Install Linux build dependencies | |
| if: runner.os == 'Linux' | |
| run: | | |
| sudo apt-get update | |
| sudo apt-get install -y --no-install-recommends \ | |
| libwebkit2gtk-4.1-dev \ | |
| libgtk-3-dev \ | |
| libayatana-appindicator3-dev \ | |
| librsvg2-dev \ | |
| patchelf \ | |
| file \ | |
| build-essential | |
| # Release builds of lumen-mcp, lumen-daemon and the CLI, staged where the Tauri | |
| # build and the hook tests look for them. | |
| - name: Build sidecars | |
| working-directory: lumenator | |
| run: bash ./build-sidecar.sh | |
| - name: Hook e2e (install -> captured payload -> hook -> ledger row) | |
| run: cargo test -p Lumen --lib setup::hook_e2e -- --nocapture --test-threads=1 | |
| - name: cargo test (every crate, every target) | |
| run: cargo test --workspace --all-targets | |
| # ── Linux GUI job ────────────────────────────────────────────────────────── | |
| # Now that Lumen ships a Linux AppImage and .deb, the Tauri crate has to build | |
| # and pass on Linux too. Without this, a Linux-only break in the GUI would only | |
| # surface during a release, when the tag is already pushed. | |
| # | |
| # Sidecars are built first because `tauri_build::build()` resolves every | |
| # externalBin against the target triple and fails if one is missing — that | |
| # missing step is the reason clippy still needs `--exclude Lumen` on macOS. | |
| gui-linux: | |
| name: GUI build + test (Linux) | |
| runs-on: ubuntu-22.04 # matches the release job: libwebkit2gtk-4.1 soname | |
| steps: | |
| - uses: actions/checkout@v7 | |
| - uses: dtolnay/rust-toolchain@stable | |
| with: | |
| components: clippy | |
| - uses: swatinem/rust-cache@v2 | |
| with: | |
| workspaces: ". -> target" | |
| cache-on-failure: true | |
| - name: Install Linux build dependencies | |
| run: | | |
| sudo apt-get update | |
| sudo apt-get install -y --no-install-recommends \ | |
| libwebkit2gtk-4.1-dev \ | |
| libgtk-3-dev \ | |
| libayatana-appindicator3-dev \ | |
| librsvg2-dev \ | |
| patchelf \ | |
| file \ | |
| build-essential | |
| - name: Build sidecars | |
| working-directory: lumenator | |
| run: bash ./build-sidecar.sh | |
| - name: cargo test (Tauri crate on Linux) | |
| run: cargo test -p Lumen | |
| - name: cargo clippy (Tauri crate on Linux) | |
| run: cargo clippy -p Lumen --all-targets -- -D warnings | |
| # ── The built app, driven through WebDriver ────────────────────────────────── | |
| # The debug build of the app itself, launched by tauri-driver: whether it opens its window | |
| # when it should and only then, says why when it does, and whether the Optimizer and Hotspots | |
| # screens show what a ledger the real lumen-mcp wrote holds. lumenator/e2e/app.e2e.mjs says | |
| # what each test covers. | |
| # | |
| # Linux and Windows only. tauri-driver needs the platform's own WebDriver behind it, | |
| # WebKitWebDriver on Linux and msedgedriver on Windows; macOS has none for its web view, so | |
| # the app cannot be driven there. The job after this one launches it on macOS instead. On | |
| # Windows the tests run unelevated; the step says why. | |
| app-e2e: | |
| name: App e2e (${{ matrix.os }}) | |
| # A passing run takes about ten minutes. The job once sat in the test step for over two hours, | |
| # and without a limit it would have sat there for six. | |
| timeout-minutes: 45 | |
| strategy: | |
| fail-fast: false | |
| matrix: | |
| os: [ubuntu-22.04, windows-latest] | |
| runs-on: ${{ matrix.os }} | |
| defaults: | |
| run: | |
| shell: bash | |
| steps: | |
| - uses: actions/checkout@v7 | |
| - uses: dtolnay/rust-toolchain@stable | |
| - uses: swatinem/rust-cache@v2 | |
| with: | |
| workspaces: ". -> target" | |
| cache-on-failure: true | |
| - uses: actions/setup-node@v7 | |
| with: | |
| node-version: ${{ env.NODE_VERSION }} | |
| - uses: pnpm/action-setup@v6 | |
| with: | |
| version: ${{ env.PNPM_VERSION }} | |
| - name: Install Linux build dependencies, WebKitWebDriver and Xvfb | |
| if: runner.os == 'Linux' | |
| run: | | |
| sudo apt-get update | |
| sudo apt-get install -y --no-install-recommends \ | |
| libwebkit2gtk-4.1-dev \ | |
| libgtk-3-dev \ | |
| libayatana-appindicator3-dev \ | |
| librsvg2-dev \ | |
| patchelf \ | |
| file \ | |
| build-essential \ | |
| webkit2gtk-driver \ | |
| xvfb \ | |
| xauth | |
| - name: Install tauri-driver | |
| run: cargo install tauri-driver --version 2.1.0 --locked | |
| # msedgedriver has to match the WebView2 runtime the app runs in, which need not be the | |
| # Edge browser the runner image ships a driver for. That one is the fallback. The download | |
| # is cached by runtime version and retried; if it still fails, the step says what it was | |
| # fetching and falls back, rather than ending the job. | |
| - name: Find the WebView2 runtime | |
| if: runner.os == 'Windows' | |
| id: webview2 | |
| shell: pwsh | |
| run: | | |
| $client = 'Microsoft\EdgeUpdate\Clients\{F3017226-FE2A-4295-8BDF-00C3A9A7E4C5}' | |
| $v = $null | |
| foreach ($key in "HKLM:\SOFTWARE\WOW6432Node\$client", "HKLM:\SOFTWARE\$client", "HKCU:\Software\$client") { | |
| $pv = (Get-ItemProperty -Path $key -Name pv -ErrorAction SilentlyContinue).pv | |
| if ($pv) { $v = $pv; Write-Output "WebView2 runtime $v ($key)"; break } | |
| } | |
| "version=$v" | Out-File -FilePath $env:GITHUB_OUTPUT -Append -Encoding utf8 | |
| - name: Restore msedgedriver | |
| if: runner.os == 'Windows' && steps.webview2.outputs.version != '' | |
| uses: actions/cache@v6 | |
| with: | |
| path: ${{ runner.temp }}/msedgedriver | |
| key: msedgedriver-win64-${{ steps.webview2.outputs.version }} | |
| - name: Fetch the msedgedriver of the WebView2 runtime | |
| if: runner.os == 'Windows' | |
| shell: pwsh | |
| run: | | |
| . ./scripts/ci-download.ps1 | |
| $v = '${{ steps.webview2.outputs.version }}' | |
| $dir = Join-Path $env:RUNNER_TEMP 'msedgedriver' | |
| $driver = Join-Path $dir 'msedgedriver.exe' | |
| try { | |
| if (-not $v) { throw 'no WebView2 runtime version in the registry' } | |
| if (-not (Test-Path $driver)) { | |
| Get-WithRetry "https://msedgedriver.microsoft.com/$v/edgedriver_win64.zip" "$dir.zip" "msedgedriver $v" | |
| Expand-Archive "$dir.zip" -DestinationPath $dir -Force | |
| } | |
| } catch { | |
| Write-Output "no msedgedriver $v to download ($_); using the runner image's" | |
| $driver = Join-Path $env:EDGEWEBDRIVER 'msedgedriver.exe' | |
| } | |
| & $driver --version | |
| "LUMEN_E2E_NATIVE_DRIVER=$driver" | Out-File -FilePath $env:GITHUB_ENV -Append -Encoding utf8 | |
| - name: Build sidecars | |
| working-directory: lumenator | |
| run: bash ./build-sidecar.sh | |
| - name: pnpm install | |
| working-directory: lumenator | |
| run: pnpm install --frozen-lockfile | |
| - name: Build the app (debug, not bundled) | |
| working-directory: lumenator | |
| run: pnpm tauri build --debug --no-bundle | |
| - name: App e2e | |
| if: runner.os == 'Linux' | |
| working-directory: lumenator | |
| run: xvfb-run -a node --test 'e2e/*.e2e.mjs' | |
| - name: Restore gsudo | |
| if: runner.os == 'Windows' | |
| uses: actions/cache@v6 | |
| with: | |
| path: ${{ runner.temp }}/gsudo.portable.zip | |
| key: gsudo-2.6.1-21130BF1 | |
| - name: Fetch gsudo | |
| if: runner.os == 'Windows' | |
| shell: pwsh | |
| run: | | |
| . ./scripts/ci-download.ps1 | |
| $zip = Join-Path $env:RUNNER_TEMP 'gsudo.portable.zip' | |
| if (-not (Test-Path $zip)) { | |
| Get-WithRetry https://github.com/gerardog/gsudo/releases/download/v2.6.1/gsudo.portable.zip $zip 'gsudo v2.6.1' | |
| } | |
| $sum = (Get-FileHash $zip -Algorithm SHA256).Hash | |
| if ($sum -ne '21130BF178D7B9891207F00BFF56F05B6B363CE9CEC7B2D084E45FB12EE51F44') { | |
| Remove-Item $zip | |
| throw "gsudo.portable.zip: SHA-256 $sum, not the pinned one" | |
| } | |
| Expand-Archive $zip -DestinationPath (Join-Path $env:RUNNER_TEMP 'gsudo') -Force | |
| # The runner's shell is elevated, and from WebView2 150 the web view of an elevated app | |
| # ignores WEBVIEW2_ADDITIONAL_BROWSER_ARGUMENTS, the variable msedgedriver opens its | |
| # debugging port through: no session attaches, and every test fails with "DevToolsActivePort | |
| # file doesn't exist" (tauri-apps/wry#1782). Lumen runs unelevated for its users, so the | |
| # tests run it that way, through gsudo at medium integrity, with what the lowered token | |
| # has to reach opened to it. | |
| # A passing run of this step takes under a minute. The limits name what hung: --test-timeout | |
| # ends a single test, which is then reported by name, and the step's own limit ends the rest. | |
| # Neither retries anything; a test that fails or hangs stays failed. | |
| - name: App e2e (unelevated) | |
| if: runner.os == 'Windows' | |
| timeout-minutes: 15 | |
| working-directory: lumenator | |
| shell: pwsh | |
| run: | | |
| $gsudo = Join-Path $env:RUNNER_TEMP 'gsudo\x64\gsudo.exe' | |
| icacls $env:GITHUB_WORKSPACE /grant 'Everyone:(OI)(CI)F' /Q | |
| icacls (Split-Path $env:LUMEN_E2E_NATIVE_DRIVER) /grant 'Everyone:(OI)(CI)RX' /Q | |
| $run = Join-Path $env:RUNNER_TEMP 'app-e2e.ps1' | |
| @( | |
| "Set-Location '$PWD'" | |
| "whoami /groups | Select-String 'Mandatory Label'" | |
| "if (-not `$env:LUMEN_E2E_NATIVE_DRIVER) { throw 'gsudo did not pass the environment on' }" | |
| "node --test --test-timeout=180000 'e2e/*.e2e.mjs'" | |
| 'exit $LASTEXITCODE' | |
| ) | Set-Content $run | |
| whoami /groups | Select-String 'Mandatory Label' | |
| & $gsudo --integrity Medium pwsh -NoProfile -File $run | |
| exit $LASTEXITCODE | |
| # What was still running when a step failed or timed out: the app, its daemon, the drivers. | |
| - name: What was running | |
| if: failure() && runner.os == 'Windows' | |
| shell: pwsh | |
| run: | | |
| Get-Process | Where-Object { $_.Name -match 'Lumen|lumen|tauri-driver|msedge|gsudo|node|pwsh' } | | |
| Sort-Object Name | Format-Table Id, Name, CPU, StartTime -AutoSize | Out-String -Width 200 | |
| # ── Issue #5, made on purpose ───────────────────────────────────────────────── | |
| # The bug that hid Lumen's icon was a preference only the reporter's machine held: macOS's | |
| # "NSStatusItem Visible <autosave>" set false by a ⌘-drag. This sets it, in both forms, | |
| # launches the built app, and checks the preference is cleared, the icon is on screen and the | |
| # window opens saying why; then that a launch with nothing set does none of it. It writes the | |
| # app's preferences as the logged-in user, which is why it runs here and refuses to elsewhere. | |
| issue5: | |
| name: Issue 5 launch check (macOS) | |
| runs-on: macos-14 | |
| steps: | |
| - uses: actions/checkout@v7 | |
| - uses: dtolnay/rust-toolchain@stable | |
| - uses: swatinem/rust-cache@v2 | |
| with: | |
| workspaces: ". -> target" | |
| cache-on-failure: true | |
| - uses: actions/setup-node@v7 | |
| with: | |
| node-version: ${{ env.NODE_VERSION }} | |
| - uses: pnpm/action-setup@v6 | |
| with: | |
| version: ${{ env.PNPM_VERSION }} | |
| - name: Build sidecars | |
| working-directory: lumenator | |
| run: bash ./build-sidecar.sh | |
| - name: pnpm install | |
| working-directory: lumenator | |
| run: pnpm install --frozen-lockfile | |
| - name: Build the app bundle (debug) | |
| working-directory: lumenator | |
| run: pnpm tauri build --debug --bundles app | |
| - name: Plant the hidden-icon preference and launch | |
| run: bash scripts/issue5-launch-check.sh target/debug/bundle/macos/Lumen.app "$RUNNER_TEMP/issue5" | |
| - name: Keep what the launch showed | |
| if: always() | |
| uses: actions/upload-artifact@v7 | |
| with: | |
| name: issue5-launch-check | |
| path: ${{ runner.temp }}/issue5 | |
| if-no-files-found: warn | |
| # ── Minimum supported Rust ───────────────────────────────────────────────── | |
| # `rust-version` is declared once, in the workspace Cargo.toml, at what the | |
| # dependency set requires: sqlx 0.9 needs 1.94.0. Every other job builds on | |
| # stable, so without this one the declared floor is a claim no compiler has | |
| # checked. The toolchain is read from Cargo.toml rather than written here, so | |
| # the two cannot drift. Linux, so the Tauri crate is in the build too. | |
| msrv: | |
| name: Build on the declared rust-version | |
| runs-on: ubuntu-22.04 | |
| steps: | |
| - uses: actions/checkout@v7 | |
| - name: Read the declared rust-version | |
| id: msrv | |
| run: echo "version=$(sed -n 's/^rust-version = "\(.*\)"$/\1/p' Cargo.toml)" >> "$GITHUB_OUTPUT" | |
| - uses: dtolnay/rust-toolchain@master | |
| with: | |
| toolchain: ${{ steps.msrv.outputs.version }} | |
| - name: The compiler is the declared one, and every crate declares it | |
| run: bash scripts/check-msrv.sh | |
| - uses: swatinem/rust-cache@v2 | |
| with: | |
| workspaces: ". -> target" | |
| cache-on-failure: true | |
| - name: Install Linux build dependencies | |
| run: | | |
| sudo apt-get update | |
| sudo apt-get install -y --no-install-recommends \ | |
| libwebkit2gtk-4.1-dev \ | |
| libgtk-3-dev \ | |
| libayatana-appindicator3-dev \ | |
| librsvg2-dev \ | |
| patchelf \ | |
| file \ | |
| build-essential | |
| - name: Build sidecars | |
| working-directory: lumenator | |
| run: bash ./build-sidecar.sh | |
| - name: cargo build (every crate, every target) | |
| run: cargo build --workspace --all-targets --locked |