# Waggle — Tauri Build Verification (per-PR + main pushes) # # CC Sesija A §2.4 Task A13 (PM-reframed scope). Verifies the desktop app # builds cleanly on Win + macOS for every PR + main push, so a regression # can't sneak in unnoticed between releases. Distinct from release.yml # which only triggers on `v*` tags + uploads to GitHub Releases (this # workflow only uploads to the workflow run as artifacts for download # verification, no release publishing). # # Exit signal: green CI here means tag-push to release.yml is safe to # pull the trigger on. Red CI here = same investigation flow as release.yml # (Rust compile / Vite build / sidecar bundle / native dep failure). name: Tauri Build Verification on: pull_request: branches: - main paths: - 'app/**' - 'apps/web/**' - 'packages/**' - 'scripts/**' - 'package.json' - 'package-lock.json' - '.github/workflows/release.yml' - '.github/workflows/tauri-build-pr.yml' push: branches: - main paths: - 'app/**' - 'apps/web/**' - 'packages/**' - 'scripts/**' - 'package.json' - 'package-lock.json' - '.github/workflows/release.yml' - '.github/workflows/tauri-build-pr.yml' workflow_dispatch: permissions: contents: read jobs: verify-windows: runs-on: windows-latest timeout-minutes: 45 steps: - uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4 with: persist-credentials: false - name: Setup Node.js uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4 with: node-version: 22.23.2 cache: npm - name: Setup Rust uses: dtolnay/rust-toolchain@4cda84d5c5c54efe2404f9d843567869ab1699d4 with: toolchain: 1.94.0 - name: Rust cache uses: Swatinem/rust-cache@e18b497796c12c097a38f9edb9d0641fb99eee32 # v2 with: workspaces: app/src-tauri - name: Install dependencies run: npm ci - name: Verify Windows skill-audit and vault regressions shell: pwsh run: node node_modules/vitest/vitest.mjs run --root . --config vitest.config.ts packages/agent/tests/skill-audit-store.test.ts packages/core/tests/vault.test.ts --maxWorkers=1 --no-file-parallelism - name: Verify Windows release-mode and publication guards shell: pwsh env: WAGGLE_REQUIRE_PWSH7: '1' run: node node_modules/vitest/vitest.mjs run --root . --config vitest.config.ts packages/server/tests/tauri-config.test.ts -t "CI/CD Configuration" - name: Install locked Tauri CLI run: npm ci --prefix app --ignore-scripts - name: Build packages (shared → core → agent → server) run: npm run build:packages - name: Bundle Node.js runtime run: node scripts/bundle-node.mjs - name: Build sidecar run: node scripts/build-sidecar.mjs - name: Bundle native dependencies run: node scripts/bundle-native-deps.mjs - name: Stage sidecar dependencies run: node scripts/stage-sidecar-deps.mjs - name: Verify packaged hook lifecycles run: node node_modules/vitest/vitest.mjs run --root . --config vitest.config.ts packages/agent/tests/hook-packages-runtime.test.ts -t "runs staged Tauri hook lifecycles" env: WAGGLE_VERIFY_STAGED_HOOK_RUNTIME: '1' - name: Build frontend run: cd apps/web && npx vite build - name: Verify repository cleanliness before Tauri build shell: pwsh run: | $sourceStatus = @(& git status --porcelain=v1 --untracked-files=all) if ($LASTEXITCODE -ne 0) { throw 'Could not inspect repository source state before Tauri build.' } if ($sourceStatus.Count -ne 0) { $sourceDetails = $sourceStatus -join [Environment]::NewLine throw "Repository must be clean before Windows Tauri build.$([Environment]::NewLine)$sourceDetails" } - name: Build Tauri (Windows) id: tauri-build-windows # app/package-lock.json pins the CLI and platform binary. Invoke that # local copy so verification builds cannot drift to a newer 2.x release. run: cd app && node node_modules/@tauri-apps/cli/tauri.js build --bundles nsis env: # Skip code signing for PR verification — release.yml handles signing # only on tag push. TAURI_PRIVATE_KEY: '' TAURI_KEY_PASSWORD: '' - name: Report repository changes after Tauri build if: ${{ always() && steps.tauri-build-windows.outcome != 'skipped' }} shell: pwsh run: | $sourceStatus = @(& git status --porcelain=v1 --untracked-files=all) if ($LASTEXITCODE -ne 0) { throw 'Could not inspect repository source state after Tauri build.' } if ($sourceStatus.Count -ne 0) { $trackedPaths = @(& git diff --name-only --diff-filter=ACDMRTUXB) if ($LASTEXITCODE -ne 0) { throw 'Could not enumerate tracked Tauri build mutations.' } foreach ($trackedPath in $trackedPaths) { Write-Host "::group::Tracked mutation: $trackedPath" $headBlob = @(& git rev-parse "HEAD:$trackedPath" 2>&1) -join '' $headBlobExit = $LASTEXITCODE $indexBlob = @(& git rev-parse ":$trackedPath" 2>&1) -join '' $indexBlobExit = $LASTEXITCODE if (Test-Path -LiteralPath $trackedPath -PathType Leaf) { $worktreeHash = (Get-FileHash -LiteralPath $trackedPath -Algorithm SHA256).Hash.ToLowerInvariant() $filteredBlob = @(& git hash-object "--path=$trackedPath" -- $trackedPath 2>&1) -join '' $filteredBlobExit = $LASTEXITCODE $resolvedPath = (Resolve-Path -LiteralPath $trackedPath).Path $bytes = [IO.File]::ReadAllBytes($resolvedPath) $crlfCount = 0 $lfOnlyCount = 0 for ($index = 0; $index -lt $bytes.Length; $index += 1) { if ($bytes[$index] -ne 10) { continue } if ($index -gt 0 -and $bytes[$index - 1] -eq 13) { $crlfCount += 1 } else { $lfOnlyCount += 1 } } } else { $worktreeHash = '' $filteredBlob = '' $filteredBlobExit = 0 $crlfCount = 0 $lfOnlyCount = 0 } Write-Host "HEAD blob (exit $headBlobExit): $headBlob" Write-Host "Index blob (exit $indexBlobExit): $indexBlob" Write-Host "Git-filtered worktree blob (exit $filteredBlobExit): $filteredBlob" Write-Host "Raw worktree SHA256: $worktreeHash; CRLF=$crlfCount; LF-only=$lfOnlyCount" $changeSummary = @(& git diff --numstat -- $trackedPath) $changeSummaryExit = $LASTEXITCODE if ($changeSummaryExit -ne 0) { Write-Host "Could not render numeric diff summary (exit $changeSummaryExit)." } else { Write-Host "Numeric diff summary: $($changeSummary -join '; ')" } Write-Host '::endgroup::' } $sourceDetails = $sourceStatus -join [Environment]::NewLine throw "Windows Tauri build mutated repository worktree.$([Environment]::NewLine)$sourceDetails" } - name: Certify Windows Solo installer lifecycle shell: pwsh run: | $installers = @(Get-ChildItem -LiteralPath 'app/src-tauri/target' -Recurse -Filter '*-setup.exe' -File | Where-Object { $_.DirectoryName -match '[\\/]bundle[\\/]nsis$' }) if ($installers.Count -ne 1) { throw "Expected exactly one NSIS setup executable, found $($installers.Count)" } $installer = $installers[0] & ./scripts/certify-windows-installer.ps1 -InstallerPath $installer.FullName -ExpectedSourceRevision $env:GITHUB_SHA - name: Upload Windows artifacts uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4 with: name: waggle-windows-${{ github.sha }} path: | app/src-tauri/target/**/bundle/nsis/*.exe app/src-tauri/target/**/bundle/nsis/windows-installer-certificate.json if-no-files-found: error retention-days: 7 verify-macos: timeout-minutes: 60 strategy: # Per-arch, matching release.yml. Universal builds are rejected by the # bundle scripts (sqlite-vec / onnxruntime / node ship per-arch binaries), # so each arch is staged and built separately. matrix: include: - target: aarch64-apple-darwin arch: arm64 runner: macos-15 bundles: dmg artifact_path: app/src-tauri/target/aarch64-apple-darwin/release/bundle/dmg/*.dmg - target: x86_64-apple-darwin arch: x64 runner: macos-15-intel bundles: app artifact_path: app/src-tauri/target/x86_64-apple-darwin/release/bundle/macos/*.app runs-on: ${{ matrix.runner }} env: TARGET_ARCH: ${{ matrix.arch }} steps: - uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4 with: persist-credentials: false - name: Setup Node.js uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4 with: node-version: 22.23.2 cache: npm - name: Verify runner architecture run: node -e "if (process.arch !== process.env.TARGET_ARCH) { console.error('Expected ' + process.env.TARGET_ARCH + ' runner, got ' + process.arch); process.exit(1); }" - name: Setup Rust uses: dtolnay/rust-toolchain@4cda84d5c5c54efe2404f9d843567869ab1699d4 with: toolchain: 1.94.0 targets: ${{ matrix.target }} - name: Rust cache uses: Swatinem/rust-cache@e18b497796c12c097a38f9edb9d0641fb99eee32 # v2 with: workspaces: app/src-tauri - name: Install dependencies run: npm ci - name: Install locked Tauri CLI run: npm ci --prefix app --ignore-scripts - name: Build packages (shared → core → agent → server) run: npm run build:packages - name: Bundle Node.js runtime run: node scripts/bundle-node.mjs - name: Build sidecar run: node scripts/build-sidecar.mjs - name: Bundle native dependencies run: node scripts/bundle-native-deps.mjs - name: Stage sidecar dependencies run: node scripts/stage-sidecar-deps.mjs - name: Build frontend run: cd apps/web && npx vite build - name: Build Tauri (macOS ${{ matrix.target }}) # See verify-windows note: use the app-local lockfile-pinned CLI. # Built per-arch — universal is rejected by the bundle scripts # (per-arch native modules), matching release.yml. run: cd app && node node_modules/@tauri-apps/cli/tauri.js build --target ${{ matrix.target }} --bundles ${{ matrix.bundles }} env: TAURI_PRIVATE_KEY: '' TAURI_KEY_PASSWORD: '' - name: Upload macOS artifacts uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4 with: name: waggle-macos-${{ matrix.target }}-${{ github.sha }} path: ${{ matrix.artifact_path }} if-no-files-found: error retention-days: 7