feat: consolidate TraceDecay V2 PR8-PR13 delivery #3146
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: [master, feature/holographic-memory] | |
| pull_request: | |
| branches: ['**'] | |
| permissions: | |
| contents: read | |
| concurrency: | |
| group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }} | |
| cancel-in-progress: true | |
| env: | |
| CARGO_TERM_COLOR: always | |
| CARGO_INCREMENTAL: "0" | |
| CARGO_PROFILE_DEV_DEBUG: "0" | |
| CARGO_PROFILE_TEST_DEBUG: "0" | |
| AST_GREP_VERSION: "0.44.0" | |
| # Every Rust job downloads the dashboard built once by dashboard-assets. | |
| TRACEDECAY_SKIP_DASHBOARD_BUILD: "1" | |
| jobs: | |
| dashboard-assets: | |
| name: Build dashboard artifact | |
| runs-on: ubuntu-latest | |
| # Every Rust job needs this one, so it stays tight: a wedged `rsbuild build` | |
| # should fail the matrix quickly rather than sit. This step list ran inside | |
| # 15 minutes before the accessibility gates were (wrongly) added here; the | |
| # only addition since is the payload budget check, measured at ~3s locally. | |
| timeout-minutes: 16 | |
| steps: | |
| - uses: actions/checkout@v7 | |
| - uses: actions/setup-node@v5 | |
| with: | |
| node-version: '22' | |
| cache: npm | |
| cache-dependency-path: dashboard/package-lock.json | |
| - name: Install dashboard dependencies | |
| working-directory: dashboard | |
| run: npm ci | |
| - name: Build dashboard | |
| working-directory: dashboard | |
| run: npm run build | |
| # `build.rs` writes this stamp when it drives the npm build; the direct | |
| # npm build above skips it, so reproduce the exact stamp with the same | |
| # Rust module. Downstream consumers (dogfood, freshness checks) require | |
| # the artifact to ship a stamp that truthfully matches these sources. | |
| - name: Write dashboard source stamp | |
| run: | | |
| rustc --edition 2024 -O build-support/write_dashboard_stamp.rs \ | |
| -o "${RUNNER_TEMP}/write-dashboard-stamp" | |
| "${RUNNER_TEMP}/write-dashboard-stamp" . > dashboard/app-dist/.source-stamp | |
| - name: Test dashboard bundle guard | |
| run: bash tests/dashboard_bundle_check_test.sh | |
| - name: Validate dashboard bundle | |
| run: python3 scripts/check-dashboard-bundle.py dashboard/app-dist | |
| - name: Upload dashboard artifact | |
| uses: actions/upload-artifact@v6 | |
| with: | |
| name: dashboard-app-dist | |
| path: dashboard/app-dist | |
| if-no-files-found: error | |
| include-hidden-files: true | |
| retention-days: 7 | |
| # WCAG 2.2 AA is a plan 11 acceptance criterion, so the accessibility gates | |
| # run in CI rather than only on a developer's machine. Both harnesses exit | |
| # nonzero on an axe violation, an uncaught page error, or a failed state | |
| # assertion — a clean scan of a falsified reading is not a pass. | |
| # | |
| # This is a job rather than three more steps in dashboard-assets. The whole | |
| # Rust matrix declares `needs: dashboard-assets`, so an accessibility failure | |
| # placed there skipped every Rust job and destroyed the signal about whether | |
| # Rust passed. NOTHING may declare `needs: dashboard-accessibility`. | |
| dashboard-accessibility: | |
| name: Dashboard accessibility | |
| needs: dashboard-assets | |
| if: ${{ github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == github.repository || contains(fromJSON('["master","feature/holographic-memory"]'), github.event.pull_request.base.ref) }} | |
| runs-on: ubuntu-latest | |
| # Non-blocking by owner decision 2026-07-31: axe findings stay visible in | |
| # the run summary but no longer gate the branch. Zero-axe remains a plan 11 | |
| # acceptance criterion checked before release, not per push. | |
| continue-on-error: true | |
| # Locally axe:audit takes ~3.3 min and axe:explorer ~1.3 min, and each one | |
| # runs its OWN full `rsbuild build` (see dashboard/e2e/axe-harness.ts: it | |
| # snapshots app-dist, builds into it, copies the output to scratch, then | |
| # restores it). At ~3x for a 2-core runner rendering 2 themes x 3 widths | |
| # that is ~14 min, plus ~3 min for the Chromium and system-dependency | |
| # install and ~1 min for npm ci. 30 leaves headroom for a slow runner while | |
| # still cutting off a hung browser or wedged static server. | |
| timeout-minutes: 30 | |
| steps: | |
| - uses: actions/checkout@v7 | |
| # The harness rebuilds its own bundle, but it restores app-dist to | |
| # exactly what it found. Downloading the validated artifact keeps that | |
| # restore honest, and `needs:` keeps this gate from reporting a second | |
| # red when the real fault is that the bundle never built. | |
| - name: Download dashboard artifact | |
| uses: actions/download-artifact@v7 | |
| with: | |
| name: dashboard-app-dist | |
| path: dashboard/app-dist | |
| - uses: actions/setup-node@v5 | |
| with: | |
| node-version: '22' | |
| cache: npm | |
| cache-dependency-path: dashboard/package-lock.json | |
| - name: Install dashboard dependencies | |
| working-directory: dashboard | |
| run: npm ci | |
| - name: Install Playwright Chromium | |
| working-directory: dashboard | |
| run: npx playwright install --with-deps chromium | |
| - name: Accessibility gate (workspaces) | |
| working-directory: dashboard | |
| run: npm run axe:audit | |
| - name: Accessibility gate (Explorer) | |
| working-directory: dashboard | |
| run: npm run axe:explorer | |
| commit-messages: | |
| name: Commit Messages | |
| runs-on: ubuntu-latest | |
| # Advisory by owner decision 2026-08-01: subject style must not block | |
| # end-to-end delivery; findings stay visible in the run summary. | |
| steps: | |
| - uses: actions/checkout@v7 | |
| with: | |
| fetch-depth: 0 | |
| - name: Test release PR integrity guard | |
| run: bash tests/release_pr_integrity_test.sh | |
| - name: Reject tracked ignored files | |
| run: scripts/check-release-pr-integrity.sh HEAD HEAD | |
| - name: Validate PR commit messages | |
| if: github.event_name == 'pull_request' | |
| continue-on-error: true | |
| env: | |
| BASE_SHA: ${{ github.event.pull_request.base.sha }} | |
| HEAD_SHA: ${{ github.event.pull_request.head.sha }} | |
| run: scripts/check-conventional-commits.sh "$BASE_SHA..$HEAD_SHA" | |
| - name: Validate pushed commit messages | |
| if: github.event_name == 'push' | |
| continue-on-error: true | |
| env: | |
| BEFORE_SHA: ${{ github.event.before }} | |
| HEAD_SHA: ${{ github.sha }} | |
| run: | | |
| set -euo pipefail | |
| if [ "$BEFORE_SHA" = "0000000000000000000000000000000000000000" ]; then | |
| if git rev-parse "${HEAD_SHA}^" >/dev/null 2>&1; then | |
| RANGE="${HEAD_SHA}^..${HEAD_SHA}" | |
| else | |
| RANGE="$HEAD_SHA" | |
| fi | |
| else | |
| RANGE="${BEFORE_SHA}..${HEAD_SHA}" | |
| fi | |
| scripts/check-conventional-commits.sh "$RANGE" | |
| release-version-drift: | |
| name: Release Version Drift | |
| # The dogfood command contract test below exercises the checkout's built | |
| # dashboard bundle and its .source-stamp, so this job consumes the same | |
| # artifact every Rust job uses instead of rebuilding the dashboard. | |
| needs: dashboard-assets | |
| # Also runs on push so the workflow-contract tests below actually protect | |
| # master, which is the branch they exist to protect. `on.push.branches` is | |
| # already limited to master and feature/holographic-memory, and the | |
| # concurrency group keys pushes by ref and PRs by number, so this adds no | |
| # duplicate or competing run. | |
| if: >- | |
| github.event_name == 'push' || | |
| (github.event_name == 'pull_request' && | |
| !startsWith(github.head_ref, 'release-plz-')) | |
| runs-on: ubuntu-latest | |
| steps: | |
| - uses: actions/checkout@v7 | |
| - name: Download dashboard artifact | |
| uses: actions/download-artifact@v7 | |
| with: | |
| name: dashboard-app-dist | |
| path: dashboard/app-dist | |
| - uses: actions/setup-python@v6 | |
| with: | |
| python-version: "3.12" | |
| - name: Test release version drift guard | |
| run: bash tests/release_drift_check_test.sh | |
| - name: Check release safety guards | |
| run: bash tests/release_safety_test.sh | |
| - name: Check production feature profile | |
| run: python3 scripts/check-production-feature-profile.py | |
| - name: Test portable package harnesses | |
| run: | | |
| python3 scripts/test-resolve-installed-binary.py | |
| python3 scripts/test-check-packaged-mcp-stdio.py | |
| python3 scripts/test-resolve-release-source-profile.py | |
| python3 scripts/test-scoop-manifest-workflows.py | |
| - name: Test local dogfood command contract | |
| run: bash tests/dogfood_command_test.sh | |
| - name: Check release version drift | |
| continue-on-error: true | |
| # PR-only, unchanged. This one queries crates.io and fails whenever | |
| # Cargo.toml is ahead of the registry, which is the normal transient | |
| # state on master between a release-plz merge and the publish | |
| # completing. Running it on master push would go red every release for | |
| # reasons unrelated to the commit. | |
| if: github.event_name == 'pull_request' | |
| run: scripts/check-release-drift.sh | |
| test: | |
| name: Test ${{ matrix.name }} | |
| needs: dashboard-assets | |
| if: ${{ github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == github.repository || contains(fromJSON('["master","feature/holographic-memory"]'), github.event.pull_request.base.ref) }} | |
| runs-on: ${{ fromJSON(matrix.runner) }} | |
| env: | |
| RUSTFLAGS: ${{ matrix.name == 'Linux' && '--cfg tracedecay_observation_fault_harness' || '' }} | |
| strategy: | |
| fail-fast: false | |
| matrix: | |
| include: | |
| - name: Linux | |
| runner: '"ubuntu-latest"' | |
| - name: macOS | |
| runner: '"macos-14"' | |
| steps: | |
| - uses: actions/checkout@v7 | |
| - name: Download dashboard artifact | |
| uses: actions/download-artifact@v7 | |
| with: | |
| name: dashboard-app-dist | |
| path: dashboard/app-dist | |
| - uses: dtolnay/rust-toolchain@stable | |
| - uses: ./.github/actions/setup-linux-mold | |
| - uses: actions/setup-node@v5 | |
| with: | |
| node-version: 22 | |
| - uses: actions/setup-python@v6 | |
| with: | |
| python-version: "3.12" | |
| - name: Install ast-grep | |
| uses: ./.github/actions/install-ast-grep | |
| with: | |
| version: ${{ env.AST_GREP_VERSION }} | |
| - name: Install cargo-nextest | |
| uses: taiki-e/install-action@nextest | |
| - name: Cache Rust build outputs | |
| uses: Swatinem/rust-cache@v2 | |
| with: | |
| shared-key: ci-test-full-${{ runner.os }} | |
| cache-on-failure: true | |
| # `ort-sys` (via fastembed -> ort, enabled by `--all-features`) downloads a | |
| # prebuilt ONNX Runtime in its build script and then emits an absolute | |
| # `cargo:rustc-link-search` into the *runner-local* ORT cache directory. | |
| # That directory is NOT one of the paths rust-cache stores, but the | |
| # build-script output that points at it IS (it lives under `target/`). So | |
| # a restored cache replays a link search path that does not exist on this | |
| # fresh runner, cargo still considers the build script fresh and never | |
| # re-downloads, and codegen fails with `could not find native static | |
| # library 'onnxruntime'`. Evicting just this one package forces the build | |
| # script to rerun and re-download into this runner, keeping the link path | |
| # and the binaries consistent. Keep this before the first cargo | |
| # invocation of the job. | |
| - name: Refresh ONNX Runtime binaries for ort-sys | |
| run: cargo clean -p ort-sys --locked | |
| - name: Check default-feature workspace | |
| run: cargo check --workspace --locked | |
| - name: Run tests | |
| run: cargo nextest run --workspace --all-features --profile ci --locked | |
| # Raw retention is unconditional and positioned before later gates that | |
| # can abort the job. This cannot rescue the job - a missing file is a | |
| # warning, not a pass - it only preserves whatever nextest managed to | |
| # write. | |
| - name: Upload raw nextest junit | |
| if: always() | |
| uses: actions/upload-artifact@v6 | |
| with: | |
| name: nextest-junit-${{ matrix.name == 'Linux' && 'linux' || 'macos' }} | |
| path: target/nextest/ci/junit.xml | |
| if-no-files-found: warn | |
| retention-days: 7 | |
| - name: Cursor native extension check/test/package | |
| run: | | |
| set -euo pipefail | |
| npm --prefix plugin/cursor-native-extension ci | |
| npm --prefix plugin/cursor-native-extension run check | |
| npm --prefix plugin/cursor-native-extension test | |
| npm --prefix plugin/cursor-native-extension run package | |
| # These three gates filter by test name, and libtest exits 0 when a filter | |
| # matches nothing (unlike the nextest used elsewhere, which has | |
| # --no-tests=fail). require-exact-test.sh asserts the count libtest | |
| # reports, so a rename, a moved #[cfg], or a feature change fails here | |
| # instead of turning the gate into a silent no-op that reports success. | |
| - name: Lite feature compile check | |
| # The ONLY --no-default-features build lane; the exact-test it once | |
| # ran was deleted, but lite-profile compile coverage stays. | |
| run: cargo check --locked --no-default-features --features lite | |
| - name: Host-bundle default-feature platform lifecycle | |
| run: | | |
| set -euo pipefail | |
| # The ONLY default-feature (not --all-features) lifecycle run. | |
| scripts/require-exact-test.sh \ | |
| cargo test --locked --test pr13_host_bundle_acceptance \ | |
| receipt_backed_doctor_checks_deployed_digests_registration_and_repair \ | |
| -- --exact | |
| - name: FastEmbed fixture pin check | |
| if: matrix.name == 'Linux' | |
| run: python3 tests/distribution/fastembed/prepare_fixture.py --check tests/distribution/fastembed | |
| - name: Validate checked-in semantic activation workload | |
| if: matrix.name == 'Linux' | |
| run: >- | |
| cargo run --locked --all-features | |
| --bin tracedecay-search-eval -- | |
| validate --repo-root . | |
| - name: Run in-flight observation crash harness | |
| if: matrix.name == 'Linux' | |
| run: >- | |
| scripts/require-exact-test.sh | |
| cargo test --locked --all-features --features test-transport | |
| --test daemon_fault_harness_test | |
| killed_daemon_retries_in_flight_claude_observation_once_via_public_apis | |
| -- --exact | |
| windows-build: | |
| name: Build Windows tests | |
| needs: dashboard-assets | |
| if: ${{ github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == github.repository || contains(fromJSON('["master","feature/holographic-memory"]'), github.event.pull_request.base.ref) }} | |
| runs-on: windows-latest | |
| timeout-minutes: 30 | |
| steps: | |
| - uses: actions/checkout@v7 | |
| - name: Download dashboard artifact | |
| uses: actions/download-artifact@v7 | |
| with: | |
| name: dashboard-app-dist | |
| path: dashboard/app-dist | |
| - name: Tune Windows runner for build I/O | |
| uses: ./.github/actions/tune-windows-runner | |
| - uses: dtolnay/rust-toolchain@stable | |
| with: | |
| components: clippy | |
| - name: Use lld-link linker | |
| shell: pwsh | |
| run: | | |
| where.exe lld-link.exe | |
| lld-link.exe --version | |
| "CARGO_TARGET_X86_64_PC_WINDOWS_MSVC_LINKER=lld-link.exe" | Out-File -FilePath $env:GITHUB_ENV -Encoding utf8 -Append | |
| - name: Install cargo-nextest | |
| uses: taiki-e/install-action@nextest | |
| - name: Cache Windows Rust build outputs | |
| uses: Swatinem/rust-cache@v2 | |
| with: | |
| shared-key: ci-test-full-windows-msvc-lld | |
| cache-on-failure: true | |
| # `ort-sys` (via fastembed -> ort, enabled by `--all-features`) downloads a | |
| # prebuilt ONNX Runtime in its build script and then emits an absolute | |
| # `cargo:rustc-link-search` into the *runner-local* ORT cache directory | |
| # (`%LOCALAPPDATA%\...\dfbin\<target>\<hash>`). That directory is NOT one of | |
| # the paths rust-cache stores, but the build-script output that points at it | |
| # IS (it lives under `target/`). So a restored cache replays a link search | |
| # path that does not exist on this fresh runner, cargo still considers the | |
| # build script fresh and never re-downloads, and codegen fails with | |
| # `could not find native static library 'onnxruntime'`. Clippy does not | |
| # resolve native libs, which is why only the archive step trips over it. | |
| # Evicting just this one package forces the build script to rerun and | |
| # re-download into this runner, keeping the link path and the binaries | |
| # consistent. Keep this before the first cargo invocation of the job. | |
| - name: Refresh ONNX Runtime binaries for ort-sys | |
| shell: pwsh | |
| run: cargo clean -p ort-sys --locked | |
| - name: Lint Windows-only Rust paths | |
| shell: pwsh | |
| run: cargo clippy --workspace --all-targets --all-features --locked -- -D warnings | |
| - name: Build nextest archive | |
| shell: pwsh | |
| run: cargo nextest archive --workspace --all-features --locked --archive-file "$env:RUNNER_TEMP/nextest-archive.tar.zst" | |
| - name: Upload nextest archive | |
| uses: actions/upload-artifact@v6 | |
| with: | |
| name: windows-nextest-archive | |
| path: ${{ runner.temp }}/nextest-archive.tar.zst | |
| retention-days: 1 | |
| compression-level: 0 | |
| windows-service-e2e: | |
| name: Test Windows native service lifecycle | |
| needs: windows-build | |
| runs-on: windows-latest | |
| timeout-minutes: 20 | |
| steps: | |
| - uses: actions/checkout@v7 | |
| - name: Download Windows nextest archive | |
| uses: actions/download-artifact@v7 | |
| with: | |
| name: windows-nextest-archive | |
| path: ${{ runner.temp }} | |
| - name: Run Windows native service E2E | |
| shell: pwsh | |
| run: | | |
| $ErrorActionPreference = "Stop" | |
| $archive = Join-Path $env:RUNNER_TEMP "nextest-archive.tar.zst" | |
| $extractRoot = Join-Path $env:RUNNER_TEMP "windows-service-e2e" | |
| New-Item -ItemType Directory -Path $extractRoot -Force | Out-Null | |
| & 7z.exe x $archive "-o$extractRoot" -y -bso0 -bsp0 | |
| if ($LASTEXITCODE -ne 0) { | |
| throw "7-Zip failed to decompress $archive" | |
| } | |
| $tar = Join-Path $extractRoot "nextest-archive.tar" | |
| & 7z.exe x $tar "-o$extractRoot" -y -bso0 -bsp0 | |
| if ($LASTEXITCODE -ne 0) { | |
| throw "7-Zip failed to extract $tar" | |
| } | |
| $binaries = @( | |
| Get-ChildItem -LiteralPath $extractRoot -Recurse -File -Filter tracedecay.exe | | |
| Where-Object { $_.FullName -notmatch '[\\/]deps[\\/]' } | |
| ) | |
| if ($binaries.Count -ne 1) { | |
| throw "expected exactly one non-deps tracedecay.exe; found $($binaries.Count): $($binaries.FullName -join ', ')" | |
| } | |
| $binary = $binaries[0].FullName | |
| & ./scripts/windows-service-e2e.ps1 -TraceDecayExe $binary | |
| if ($LASTEXITCODE -ne 0) { | |
| throw "Windows service E2E exited $LASTEXITCODE" | |
| } | |
| - name: Clean abandoned Windows test children | |
| if: always() | |
| uses: ./.github/actions/clean-windows-test-children | |
| windows-test-shard: | |
| name: Test Windows shard ${{ matrix.partition }}/5 | |
| needs: windows-build | |
| runs-on: windows-latest | |
| timeout-minutes: 20 | |
| strategy: | |
| fail-fast: false | |
| matrix: | |
| partition: [1, 2, 3, 4, 5] | |
| steps: | |
| - uses: actions/checkout@v7 | |
| - name: Tune Windows runner for test I/O | |
| uses: ./.github/actions/tune-windows-runner | |
| with: | |
| redirect-temp: "true" | |
| - uses: actions/setup-node@v5 | |
| with: | |
| node-version: 22 | |
| - uses: actions/setup-python@v6 | |
| with: | |
| python-version: "3.12" | |
| - name: Install ast-grep | |
| uses: ./.github/actions/install-ast-grep | |
| with: | |
| version: ${{ env.AST_GREP_VERSION }} | |
| - name: Install cargo-nextest | |
| uses: taiki-e/install-action@nextest | |
| - name: Download nextest archive | |
| uses: actions/download-artifact@v7 | |
| with: | |
| name: windows-nextest-archive | |
| path: ${{ runner.temp }} | |
| - name: Run Windows tests | |
| shell: pwsh | |
| # --extract-to must be the workspace so target/ lands at the same | |
| # absolute path as on the build job: integration tests bake | |
| # env!("CARGO_BIN_EXE_tracedecay") into the binaries at compile time. | |
| run: >- | |
| cargo-nextest nextest run --profile ci | |
| --archive-file "$env:RUNNER_TEMP/nextest-archive.tar.zst" | |
| --extract-to "$env:GITHUB_WORKSPACE" | |
| --workspace-remap "$env:GITHUB_WORKSPACE" | |
| --partition slice:${{ matrix.partition }}/5 | |
| --test-threads num-cpus --status-level slow | |
| - name: Clean abandoned Windows test children | |
| if: always() | |
| uses: ./.github/actions/clean-windows-test-children | |
| - name: Upload Windows nextest report | |
| if: always() | |
| uses: actions/upload-artifact@v6 | |
| with: | |
| name: windows-nextest-junit-${{ matrix.partition }} | |
| path: target/nextest/ci/junit.xml | |
| if-no-files-found: ignore | |
| retention-days: 7 | |
| windows-sqlite-production-policy: | |
| name: Test Windows SQLite production policy (WAL+NORMAL) | |
| if: ${{ needs.windows-build.result == 'success' && (github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == github.repository || contains(fromJSON('["master","feature/holographic-memory"]'), github.event.pull_request.base.ref)) }} | |
| needs: windows-build | |
| runs-on: windows-latest | |
| timeout-minutes: 30 | |
| steps: | |
| - uses: actions/checkout@v7 | |
| - name: Tune Windows runner for test I/O | |
| uses: ./.github/actions/tune-windows-runner | |
| with: | |
| redirect-temp: "true" | |
| - uses: actions/setup-node@v5 | |
| with: | |
| node-version: 22 | |
| - uses: actions/setup-python@v6 | |
| with: | |
| python-version: "3.12" | |
| - name: Install ast-grep | |
| uses: ./.github/actions/install-ast-grep | |
| with: | |
| version: ${{ env.AST_GREP_VERSION }} | |
| - name: Install cargo-nextest | |
| uses: taiki-e/install-action@nextest | |
| - name: Download nextest archive | |
| uses: actions/download-artifact@v7 | |
| with: | |
| name: windows-nextest-archive | |
| path: ${{ runner.temp }} | |
| - name: Run Windows SQLite production policy tests | |
| shell: pwsh | |
| timeout-minutes: 24 | |
| # --extract-to must be the workspace so target/ lands at the same | |
| # absolute path as on the build job: integration tests bake | |
| # env!("CARGO_BIN_EXE_tracedecay") into the binaries at compile time. | |
| run: >- | |
| cargo-nextest nextest run --profile ci | |
| --archive-file "$env:RUNNER_TEMP/nextest-archive.tar.zst" | |
| --extract-to "$env:GITHUB_WORKSPACE" | |
| --workspace-remap "$env:GITHUB_WORKSPACE" | |
| -E 'binary(=windows_durable_behavior)' | |
| --no-tests=fail --test-threads 1 --status-level slow | |
| - name: Clean abandoned Windows test children | |
| if: always() | |
| uses: ./.github/actions/clean-windows-test-children | |
| - name: Upload Windows SQLite production policy nextest report | |
| if: always() | |
| uses: actions/upload-artifact@v6 | |
| with: | |
| name: windows-sqlite-production-policy-junit-${{ github.run_id }}-${{ github.run_attempt }}-${{ github.sha }} | |
| path: target/nextest/ci/junit.xml | |
| if-no-files-found: error | |
| retention-days: 7 | |
| windows-test: | |
| name: Test Windows | |
| if: ${{ !cancelled() && (github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == github.repository || contains(fromJSON('["master","feature/holographic-memory"]'), github.event.pull_request.base.ref)) }} | |
| needs: [windows-test-shard, windows-service-e2e] | |
| runs-on: ubuntu-latest | |
| steps: | |
| - name: Check Windows jobs | |
| run: | | |
| set -euo pipefail | |
| if [ "${{ needs.windows-test-shard.result }}" != "success" ]; then | |
| echo "Windows shard result: ${{ needs.windows-test-shard.result }}" | |
| exit 1 | |
| fi | |
| if [ "${{ needs.windows-service-e2e.result }}" != "success" ]; then | |
| echo "Windows service E2E result: ${{ needs.windows-service-e2e.result }}" | |
| exit 1 | |
| fi | |
| echo "All Windows jobs passed." | |
| windows-platform-acceptance: | |
| name: Windows platform acceptance | |
| if: ${{ !cancelled() && needs.windows-test-shard.result == 'success' && (github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == github.repository || contains(fromJSON('["master","feature/holographic-memory"]'), github.event.pull_request.base.ref)) }} | |
| needs: [dashboard-assets, windows-test-shard] | |
| runs-on: windows-latest | |
| timeout-minutes: 20 | |
| steps: | |
| - uses: actions/checkout@v7 | |
| - name: Download dashboard artifact | |
| uses: actions/download-artifact@v7 | |
| with: | |
| name: dashboard-app-dist | |
| path: dashboard/app-dist | |
| - uses: actions/setup-node@v5 | |
| with: | |
| node-version: 22 | |
| - uses: actions/setup-python@v6 | |
| with: | |
| python-version: "3.12" | |
| - name: Download Windows nextest junit shards | |
| uses: actions/download-artifact@v7 | |
| with: | |
| pattern: windows-nextest-junit-* | |
| path: windows-junit | |
| merge-multiple: true | |
| - name: Cursor native extension check/test/package | |
| shell: bash | |
| run: | | |
| set -euo pipefail | |
| npm --prefix plugin/cursor-native-extension ci | |
| npm --prefix plugin/cursor-native-extension run check | |
| npm --prefix plugin/cursor-native-extension test | |
| npm --prefix plugin/cursor-native-extension run package | |
| - uses: dtolnay/rust-toolchain@stable | |
| - name: Cache Windows Rust build outputs | |
| uses: Swatinem/rust-cache@v2 | |
| with: | |
| shared-key: ci-pr12-pr13-lite-windows-msvc | |
| cache-on-failure: true | |
| # Same libtest no-op hazard as the Linux/macOS `test` job: `cargo test | |
| # <name> -- --exact` exits 0 when the filter matches nothing. | |
| - name: Lite feature compile check | |
| shell: bash | |
| run: cargo check --locked --no-default-features --features lite | |
| - name: Host-bundle default-feature platform lifecycle | |
| shell: bash | |
| run: cargo test --locked --test pr13_host_bundle_acceptance | |
| - name: Merge Windows junit | |
| shell: bash | |
| run: | | |
| set -euo pipefail | |
| mapfile -t junit_files < <(find windows-junit -type f -name 'junit.xml' | sort) | |
| if [[ "${#junit_files[@]}" -eq 0 ]]; then | |
| echo "missing Windows nextest junit evidence" >&2 | |
| exit 1 | |
| fi | |
| # Merge shard junit into one retained Windows runtime artifact. | |
| python3 - <<'PY' | |
| from pathlib import Path | |
| import xml.etree.ElementTree as ET | |
| root = ET.Element("testsuites") | |
| for path in sorted(Path("windows-junit").rglob("junit.xml")): | |
| tree = ET.parse(path) | |
| node = tree.getroot() | |
| if node.tag.endswith("testsuites"): | |
| for child in list(node): | |
| root.append(child) | |
| else: | |
| root.append(node) | |
| Path("windows-merged-junit.xml").write_bytes(ET.tostring(root, encoding="utf-8")) | |
| PY | |
| - name: Upload Windows runtime junit | |
| uses: actions/upload-artifact@v6 | |
| with: | |
| name: runtime-junit-windows | |
| path: windows-merged-junit.xml | |
| retention-days: 7 | |
| # Catches serving-path performance regressions — the class profiled on | |
| # 2026-08-01, where a search went from milliseconds to minutes because | |
| # per-request work scaled with store size. The script indexes THIS repo into | |
| # a throwaway profile, serves it from a private daemon, and drives six | |
| # concurrent readers at it; the pass/fail budgets live in one block at the | |
| # top of scripts/perf-gate.sh. They are order-of-magnitude tripwires, so a | |
| # red here means a real regression, not a noisy runner. | |
| perf-gate: | |
| name: Serving-path perf gate | |
| needs: dashboard-assets | |
| if: ${{ github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == github.repository || contains(fromJSON('["master","feature/holographic-memory"]'), github.event.pull_request.base.ref) }} | |
| runs-on: ubuntu-latest | |
| # The gate needs an OPTIMIZED binary: a debug build makes the CPU-bound | |
| # index and query paths slow enough to trip the budgets for reasons that | |
| # have nothing to do with a regression. That means a release build of the | |
| # root crate (~9 min on a 96-core box, longer here) plus a ~2 min index and | |
| # a 60 s load window. rust-cache carries the dependency graph between runs; | |
| # the root crate itself always recompiles. | |
| timeout-minutes: 60 | |
| steps: | |
| - uses: actions/checkout@v7 | |
| - name: Download dashboard artifact | |
| uses: actions/download-artifact@v7 | |
| with: | |
| name: dashboard-app-dist | |
| path: dashboard/app-dist | |
| - uses: dtolnay/rust-toolchain@stable | |
| - uses: ./.github/actions/setup-linux-mold | |
| - name: Cache Rust build outputs | |
| uses: Swatinem/rust-cache@v2 | |
| with: | |
| shared-key: ci-perf-gate-release-${{ runner.os }} | |
| cache-on-failure: true | |
| # `ort-sys` (via fastembed -> ort, enabled by `--all-features`) downloads a | |
| # prebuilt ONNX Runtime in its build script and then emits an absolute | |
| # `cargo:rustc-link-search` into the *runner-local* ORT cache directory. | |
| # That directory is NOT one of the paths rust-cache stores, but the | |
| # build-script output that points at it IS (it lives under `target/`). So | |
| # a restored cache replays a link search path that does not exist on this | |
| # fresh runner, cargo still considers the build script fresh and never | |
| # re-downloads, and codegen fails with `could not find native static | |
| # library 'onnxruntime'`. Evicting just this one package forces the build | |
| # script to rerun and re-download into this runner, keeping the link path | |
| # and the binaries consistent. Keep this before the first cargo | |
| # invocation of the job. | |
| - name: Refresh ONNX Runtime binaries for ort-sys | |
| run: cargo clean -p ort-sys --locked | |
| - name: Run serving-path perf gate | |
| run: scripts/perf-gate.sh | |
| # Retained unconditionally: the metrics from a FAILING run are the whole | |
| # point of the artifact, and a timed-out job still leaves whatever the | |
| # verdict phase managed to write. | |
| - name: Upload perf gate metrics | |
| if: always() | |
| uses: actions/upload-artifact@v6 | |
| with: | |
| name: perf-gate-metrics | |
| path: | | |
| target/perf-gate/perf-gate-metrics.json | |
| target/perf-gate/perf-gate-summary.md | |
| if-no-files-found: warn | |
| retention-days: 14 | |
| release-compatibility: | |
| name: Release compatibility | |
| needs: dashboard-assets | |
| if: ${{ github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == github.repository || contains(fromJSON('["master","feature/holographic-memory"]'), github.event.pull_request.base.ref) }} | |
| runs-on: ubuntu-latest | |
| steps: | |
| - uses: actions/checkout@v7 | |
| - name: Download dashboard artifact | |
| uses: actions/download-artifact@v7 | |
| with: | |
| name: dashboard-app-dist | |
| path: dashboard/app-dist | |
| - uses: dtolnay/rust-toolchain@stable | |
| - uses: ./.github/actions/setup-linux-mold | |
| - name: Validate publishable package manifests | |
| # Package the workspace as one dependency-ordered batch because the V2 | |
| # crates do not exist in the registry until release-plz publishes them. | |
| run: | | |
| set -euo pipefail | |
| cargo package --workspace --locked --allow-dirty --no-verify | |
| version="$( | |
| cargo metadata --no-deps --locked --format-version 1 | | |
| jq -r '.packages[] | select(.name == "tracedecay") | .version' | |
| )" | |
| root_archive="target/package/tracedecay-${version}.crate" | |
| test -s "$root_archive" | |
| tar tzf "$root_archive" > root-archive-contents.txt | |
| grep -Fq "/build-support/dashboard_cache.rs" root-archive-contents.txt | |
| grep -Fq "/dashboard/app-dist/" root-archive-contents.txt | |
| package_root="${RUNNER_TEMP}/tracedecay-package" | |
| rm -rf "$package_root" | |
| mkdir -p "$package_root" | |
| tar xzf "$root_archive" -C "$package_root" | |
| python3 scripts/check-dashboard-bundle.py \ | |
| "$package_root/tracedecay-${version}/dashboard/app-dist" | |
| - name: Check root crate API compatibility | |
| # Advisory: with release-type "major" every break is pre-approved, so | |
| # a red here is informational at best. | |
| continue-on-error: true | |
| uses: obi1kenobi/cargo-semver-checks-action@v2 | |
| env: | |
| # semver-checks builds the crate from a synthetic resolver-v1 | |
| # workspace, so make dependency selection honor this job's toolchain. | |
| CARGO_RESOLVER_INCOMPATIBLE_RUST_VERSIONS: fallback | |
| with: | |
| package: tracedecay | |
| rust-toolchain: manual | |
| # The V2 storage cutover intentionally breaks the pre-V2 public API. | |
| # Release-plz owns the version bump at release time (the drift guard | |
| # forbids pre-bumping in the PR). In semver-checks vocabulary, | |
| # "major" means "breaking release" — for this 0.0.x crate that IS | |
| # the planned 0.1.0 minor-version bump (no 1.0 is implied). Declare | |
| # it so the action stops comparing against the unchanged version. | |
| release-type: major | |
| clippy: | |
| name: Clippy | |
| needs: dashboard-assets | |
| if: ${{ github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == github.repository || contains(fromJSON('["master","feature/holographic-memory"]'), github.event.pull_request.base.ref) }} | |
| runs-on: ubuntu-latest | |
| steps: | |
| - uses: actions/checkout@v7 | |
| - name: Download dashboard artifact | |
| uses: actions/download-artifact@v7 | |
| with: | |
| name: dashboard-app-dist | |
| path: dashboard/app-dist | |
| - uses: dtolnay/rust-toolchain@stable | |
| with: | |
| components: clippy | |
| - uses: ./.github/actions/setup-linux-mold | |
| - name: Cache Rust build outputs | |
| uses: Swatinem/rust-cache@v2 | |
| with: | |
| shared-key: ci-clippy-full-${{ runner.os }} | |
| cache-on-failure: true | |
| # `ort-sys` (via fastembed -> ort, enabled by `--all-features`) downloads a | |
| # prebuilt ONNX Runtime in its build script and then emits an absolute | |
| # `cargo:rustc-link-search` into the *runner-local* ORT cache directory. | |
| # That directory is NOT one of the paths rust-cache stores, but the | |
| # build-script output that points at it IS (it lives under `target/`). So | |
| # a restored cache replays a link search path that does not exist on this | |
| # fresh runner, cargo still considers the build script fresh and never | |
| # re-downloads, and codegen fails with `could not find native static | |
| # library 'onnxruntime'`. Evicting just this one package forces the build | |
| # script to rerun and re-download into this runner, keeping the link path | |
| # and the binaries consistent. Keep this before the first cargo | |
| # invocation of the job. | |
| - name: Refresh ONNX Runtime binaries for ort-sys | |
| run: cargo clean -p ort-sys --locked | |
| - name: Run blocking Clippy policy with default features | |
| run: cargo clippy --workspace --all-targets --locked -- -D warnings | |
| - name: Run blocking Clippy policy with all features | |
| run: cargo clippy --workspace --all-targets --all-features --locked -- -D warnings | |
| - name: Run blocking Clippy policy without default features | |
| run: cargo clippy --workspace --all-targets --no-default-features --locked -- -D warnings | |
| - name: Run blocking Clippy policy with production features | |
| run: cargo clippy --no-default-features --features production --locked -- -D warnings | |
| fmt: | |
| name: Format | |
| if: ${{ github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == github.repository || contains(fromJSON('["master","feature/holographic-memory"]'), github.event.pull_request.base.ref) }} | |
| runs-on: ubuntu-latest | |
| steps: | |
| - uses: actions/checkout@v7 | |
| - uses: dtolnay/rust-toolchain@stable | |
| with: | |
| components: rustfmt | |
| - run: cargo fmt --all -- --check | |
| dashboard: | |
| name: Dashboard integration | |
| needs: dashboard-assets | |
| if: ${{ github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == github.repository || contains(fromJSON('["master","feature/holographic-memory"]'), github.event.pull_request.base.ref) }} | |
| runs-on: ubuntu-latest | |
| timeout-minutes: 25 | |
| # Frontend checks and the production build run in dashboard-assets. This job | |
| # verifies the Rust API surface against those exact downloaded bytes. | |
| steps: | |
| - uses: actions/checkout@v7 | |
| - name: Download dashboard artifact | |
| uses: actions/download-artifact@v7 | |
| with: | |
| name: dashboard-app-dist | |
| path: dashboard/app-dist | |
| - uses: dtolnay/rust-toolchain@stable | |
| - uses: ./.github/actions/setup-linux-mold | |
| - name: Install cargo-nextest | |
| uses: taiki-e/install-action@nextest | |
| - name: Cache Rust build outputs | |
| uses: Swatinem/rust-cache@v2 | |
| with: | |
| shared-key: ci-dashboard-full-${{ runner.os }} | |
| cache-on-failure: true | |
| - uses: actions/setup-node@v5 | |
| with: | |
| node-version: '22' | |
| cache: npm | |
| cache-dependency-path: dashboard/package-lock.json | |
| - name: Install dashboard dependencies | |
| working-directory: dashboard | |
| run: npm ci | |
| - name: Typecheck dashboard | |
| working-directory: dashboard | |
| run: npm run typecheck | |
| - name: Check generated wire contracts are current | |
| working-directory: dashboard | |
| run: npm run contracts:check | |
| - name: Run dashboard unit tests | |
| working-directory: dashboard | |
| run: npm test | |
| # `ort-sys` (via fastembed -> ort, enabled by `--all-features`) downloads a | |
| # prebuilt ONNX Runtime in its build script and then emits an absolute | |
| # `cargo:rustc-link-search` into the *runner-local* ORT cache directory. | |
| # That directory is NOT one of the paths rust-cache stores, but the | |
| # build-script output that points at it IS (it lives under `target/`). So | |
| # a restored cache replays a link search path that does not exist on this | |
| # fresh runner, cargo still considers the build script fresh and never | |
| # re-downloads, and codegen fails with `could not find native static | |
| # library 'onnxruntime'`. Evicting just this one package forces the build | |
| # script to rerun and re-download into this runner, keeping the link path | |
| # and the binaries consistent. Keep this before the first cargo | |
| # invocation of the job. | |
| - name: Refresh ONNX Runtime binaries for ort-sys | |
| run: cargo clean -p ort-sys --locked | |
| - name: Run dashboard integration tests | |
| run: cargo nextest run --all-features --test dashboard_api_test --no-fail-fast --locked --no-tests=fail | |
| hermes-integration: | |
| name: Hermes integration (stock) | |
| needs: dashboard-assets | |
| if: ${{ github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == github.repository || contains(fromJSON('["master","feature/holographic-memory"]'), github.event.pull_request.base.ref) }} | |
| runs-on: ubuntu-latest | |
| timeout-minutes: 30 | |
| # Advisory by owner decision 2026-08-01: depends on a third-party repo | |
| # fetch; an upstream outage must not red unrelated PRs. | |
| continue-on-error: true | |
| env: | |
| # Stock (upstream) Hermes pin — the generated plugin must keep working | |
| # against this exact upstream commit. Bump deliberately after rerunning | |
| # scripts/hermes_stock_integration.sh against the new ref locally. | |
| HERMES_UPSTREAM_REPO: https://github.com/NousResearch/hermes-agent.git | |
| HERMES_UPSTREAM_REF: 9dd9ef0ec99a87f078f7272b4323df5440b4b3f9 | |
| steps: | |
| - uses: actions/checkout@v7 | |
| - name: Download dashboard artifact | |
| uses: actions/download-artifact@v7 | |
| with: | |
| name: dashboard-app-dist | |
| path: dashboard/app-dist | |
| - uses: dtolnay/rust-toolchain@stable | |
| - uses: ./.github/actions/setup-linux-mold | |
| - name: Install ast-grep | |
| uses: ./.github/actions/install-ast-grep | |
| with: | |
| version: ${{ env.AST_GREP_VERSION }} | |
| - name: Cache Rust build outputs | |
| uses: Swatinem/rust-cache@v2 | |
| with: | |
| shared-key: ci-hermes-integration-full-${{ runner.os }} | |
| cache-on-failure: true | |
| # `ort-sys` (via fastembed -> ort, enabled by `--all-features`) downloads a | |
| # prebuilt ONNX Runtime in its build script and then emits an absolute | |
| # `cargo:rustc-link-search` into the *runner-local* ORT cache directory. | |
| # That directory is NOT one of the paths rust-cache stores, but the | |
| # build-script output that points at it IS (it lives under `target/`). So | |
| # a restored cache replays a link search path that does not exist on this | |
| # fresh runner, cargo still considers the build script fresh and never | |
| # re-downloads, and codegen fails with `could not find native static | |
| # library 'onnxruntime'`. Evicting just this one package forces the build | |
| # script to rerun and re-download into this runner, keeping the link path | |
| # and the binaries consistent. Keep this before the first cargo | |
| # invocation of the job. | |
| - name: Refresh ONNX Runtime binaries for ort-sys | |
| run: cargo clean -p ort-sys --locked | |
| - name: Build tracedecay binary | |
| run: cargo build --all-features --bin tracedecay --locked | |
| - name: Run generated-plugin unit checks (no Hermes required) | |
| run: python3 scripts/hermes_plugin_unit_check.py | |
| - name: Clone stock Hermes at pinned ref | |
| run: | | |
| set -euo pipefail | |
| git init -q "$RUNNER_TEMP/hermes-upstream" | |
| git -C "$RUNNER_TEMP/hermes-upstream" remote add origin "$HERMES_UPSTREAM_REPO" | |
| git -C "$RUNNER_TEMP/hermes-upstream" fetch --depth 1 origin "$HERMES_UPSTREAM_REF" | |
| git -C "$RUNNER_TEMP/hermes-upstream" checkout --detach FETCH_HEAD | |
| - uses: astral-sh/setup-uv@v8.2.0 | |
| with: | |
| enable-cache: true | |
| cache-dependency-glob: ${{ runner.temp }}/hermes-upstream/uv.lock | |
| cache-suffix: hermes-${{ env.HERMES_UPSTREAM_REF }} | |
| - name: Set up stock Hermes environment | |
| working-directory: ${{ runner.temp }}/hermes-upstream | |
| run: uv sync --frozen --no-dev | |
| - name: Run stock Hermes integration checks | |
| env: | |
| TRACEDECAY_BIN: ${{ github.workspace }}/target/debug/tracedecay | |
| HERMES_UPSTREAM_DIR: ${{ runner.temp }}/hermes-upstream | |
| run: scripts/hermes_stock_integration.sh |