badge: refresh public endpoints #5779
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" ] | |
| workflow_dispatch: | |
| permissions: | |
| contents: read | |
| concurrency: | |
| group: ci-${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }} | |
| # Cancel superseded push runs and PR synchronize runs. Label add/remove must | |
| # not cancel an in-flight PR run: it triggers a fresh run with the new label | |
| # set without throwing away work. | |
| cancel-in-progress: ${{ github.event_name == 'push' || (github.event_name == 'pull_request' && github.event.action == 'synchronize') }} | |
| env: | |
| CARGO_TERM_COLOR: always | |
| CARGO_INCREMENTAL: 0 | |
| RUSTFLAGS: -C debuginfo=0 | |
| jobs: | |
| # Detect which risk packs the PR diff hits. Downstream gated jobs OR | |
| # this output into their `if:` so the deep lanes also run when matching | |
| # paths change, even without an explicit label. Mirrors the routing | |
| # in policy/ci-risk-packs.toml. | |
| detect: | |
| name: Detect risk packs | |
| runs-on: ubuntu-latest | |
| outputs: | |
| wasm: ${{ steps.detect.outputs.wasm }} | |
| nix: ${{ steps.detect.outputs.nix }} | |
| release: ${{ steps.detect.outputs.release }} | |
| windows_path: ${{ steps.detect.outputs.windows_path }} | |
| core_receipts: ${{ steps.detect.outputs.core_receipts }} | |
| analysis: ${{ steps.detect.outputs.analysis }} | |
| steps: | |
| - uses: actions/checkout@v6.0.2 | |
| with: | |
| fetch-depth: 0 | |
| - name: Fetch base ref | |
| if: github.event_name == 'pull_request' | |
| run: git fetch origin "${{ github.base_ref }}":"refs/remotes/origin/${{ github.base_ref }}" || true | |
| - id: detect | |
| name: Compute risk-pack output flags | |
| env: | |
| LABELS_JSON: ${{ github.event_name == 'pull_request' && toJson(github.event.pull_request.labels) || '[]' }} | |
| PUSH_BEFORE: ${{ github.event.before || '' }} | |
| run: | | |
| set -euo pipefail | |
| mkdir -p target/ci | |
| if [ "${{ github.event_name }}" = "pull_request" ]; then | |
| base_ref="origin/${{ github.base_ref }}" | |
| elif [ "${{ github.event_name }}" = "push" ] && [ -n "${PUSH_BEFORE}" ] && ! printf '%s' "${PUSH_BEFORE}" | grep -Eq '^0+$'; then | |
| base_ref="${PUSH_BEFORE}" | |
| elif [ "${{ github.event_name }}" = "push" ] && git rev-parse --verify HEAD^ >/dev/null 2>&1; then | |
| base_ref="HEAD^" | |
| elif git rev-parse --verify HEAD^ >/dev/null 2>&1; then | |
| base_ref="HEAD^" | |
| else | |
| base_ref="HEAD" | |
| fi | |
| cargo xtask ci-plan \ | |
| --base "${base_ref}" \ | |
| --head HEAD \ | |
| --labels-json "${LABELS_JSON}" \ | |
| --lanes policy/ci-lane-whitelist.toml \ | |
| --risk-packs policy/ci-risk-packs.toml \ | |
| --no-budget-annotations \ | |
| --json-out target/ci/ci-detect-plan.json \ | |
| --route-json-out target/ci/proof-pack-route.json \ | |
| --github-output "$GITHUB_OUTPUT" | |
| - name: Verify proof-pack route receipts | |
| if: always() | |
| run: | | |
| status=0 | |
| for receipt in target/ci/ci-detect-plan.json target/ci/proof-pack-route.json; do | |
| if [ ! -s "$receipt" ]; then | |
| echo "::error::Missing or empty proof-pack route receipt: $receipt" | |
| status=1 | |
| fi | |
| done | |
| exit "$status" | |
| - name: Upload proof-pack route receipt | |
| if: always() | |
| uses: actions/upload-artifact@v7 | |
| with: | |
| name: proof-pack-route | |
| path: | | |
| target/ci/ci-detect-plan.json | |
| target/ci/proof-pack-route.json | |
| if-no-files-found: error | |
| retention-days: 14 | |
| msrv: | |
| name: MSRV Check | |
| runs-on: ubuntu-latest | |
| steps: | |
| - uses: actions/checkout@v6.0.2 | |
| - name: Set up Rust (MSRV) | |
| uses: dtolnay/rust-toolchain@stable | |
| with: | |
| toolchain: 1.95.0 | |
| - uses: Swatinem/rust-cache@v2 | |
| with: | |
| save-if: ${{ github.ref == 'refs/heads/main' }} | |
| - name: Check MSRV compatibility | |
| run: cargo check --workspace --all-features | |
| build: | |
| name: Build & Test (Linux) | |
| runs-on: ubuntu-latest | |
| steps: | |
| - uses: actions/checkout@v6.0.2 | |
| - name: Set up Rust | |
| uses: dtolnay/rust-toolchain@stable | |
| - name: Use larger writable target dir | |
| run: echo "CARGO_TARGET_DIR=${RUNNER_TEMP}/target" >> "$GITHUB_ENV" | |
| - uses: Swatinem/rust-cache@v2 | |
| with: | |
| cache-directories: ${{ runner.temp }}/target | |
| save-if: ${{ github.ref == 'refs/heads/main' }} | |
| - name: Run tests | |
| run: cargo test --all-features --verbose | |
| env: | |
| CI: true | |
| # Windows full-test moves off the default PR gate. It runs on push to | |
| # main and on PRs explicitly labelled `windows` or `full-ci`. Rust path | |
| # changes that are likely to behave differently on Windows go through | |
| # the Rust-owned `git_io` risk pack output. | |
| build-windows: | |
| name: Build & Test (Windows) | |
| needs: detect | |
| if: >- | |
| github.event_name == 'push' || | |
| contains(github.event.pull_request.labels.*.name, 'windows') || | |
| contains(github.event.pull_request.labels.*.name, 'full-ci') || | |
| needs.detect.outputs.windows_path == 'true' | |
| runs-on: windows-latest | |
| steps: | |
| - uses: actions/checkout@v6.0.2 | |
| - name: Set up Rust | |
| uses: dtolnay/rust-toolchain@stable | |
| - uses: Swatinem/rust-cache@v2 | |
| with: | |
| save-if: ${{ github.ref == 'refs/heads/main' }} | |
| - name: Run tests | |
| run: cargo test --all-features --verbose | |
| env: | |
| CI: true | |
| build-macos: | |
| name: Build & Test (macOS) | |
| if: github.event_name == 'push' | |
| runs-on: macos-latest | |
| steps: | |
| - uses: actions/checkout@v6.0.2 | |
| - name: Set up Rust | |
| uses: dtolnay/rust-toolchain@stable | |
| - uses: Swatinem/rust-cache@v2 | |
| with: | |
| save-if: ${{ github.ref == 'refs/heads/main' }} | |
| cache-bin: false | |
| - name: Run tests | |
| run: cargo test --all-features --verbose | |
| env: | |
| CI: true | |
| feature-boundaries: | |
| name: Feature Boundaries | |
| runs-on: ubuntu-latest | |
| steps: | |
| - uses: actions/checkout@v6.0.2 | |
| - uses: dtolnay/rust-toolchain@stable | |
| - uses: Swatinem/rust-cache@v2 | |
| with: | |
| save-if: ${{ github.ref == 'refs/heads/main' }} | |
| cache-directories: ${{ runner.temp }}/target | |
| - name: tokmd-analysis with all features | |
| run: cargo test -p tokmd-analysis --all-features --verbose | |
| env: | |
| CI: true | |
| - name: tokmd-analysis with no default features | |
| run: cargo test -p tokmd-analysis --no-default-features --verbose | |
| env: | |
| CI: true | |
| - name: Guard analysis microcrate boundaries | |
| run: cargo xtask boundaries-check | |
| proof-policy: | |
| name: Proof Policy | |
| runs-on: ubuntu-latest | |
| steps: | |
| - uses: actions/checkout@v6.0.2 | |
| - uses: dtolnay/rust-toolchain@stable | |
| - uses: Swatinem/rust-cache@v2 | |
| with: | |
| save-if: ${{ github.ref == 'refs/heads/main' }} | |
| - name: Validate proof policy | |
| run: cargo xtask proof-policy --check | |
| affected-plan: | |
| name: Affected Proof Plan | |
| if: github.event_name == 'pull_request' | |
| runs-on: ubuntu-latest | |
| steps: | |
| - uses: actions/checkout@v6.0.2 | |
| with: | |
| fetch-depth: 0 | |
| - uses: dtolnay/rust-toolchain@stable | |
| - uses: Swatinem/rust-cache@v2 | |
| with: | |
| save-if: ${{ github.ref == 'refs/heads/main' }} | |
| - name: Fetch base ref | |
| run: git fetch origin "${{ github.base_ref }}":"refs/remotes/origin/${{ github.base_ref }}" || true | |
| - name: Generate affected proof artifacts | |
| run: | | |
| set +e | |
| mkdir -p target/proof | |
| cargo xtask affected --base "origin/${{ github.base_ref }}" --head HEAD --json-output target/proof/affected.json | |
| affected_status=$? | |
| cargo xtask proof --profile affected --base "origin/${{ github.base_ref }}" --head HEAD --plan --plan-json target/proof/proof-plan.json --summary-md target/proof/proof-plan.md --evidence-json target/proof/proof-evidence.json --executor-summary target/proof/executor-summary.json --executor-manifest target/proof/executor-manifest.json | |
| proof_plan_status=$? | |
| cargo xtask proof-artifacts-check --executor-summary target/proof/executor-summary.json --executor-manifest target/proof/executor-manifest.json --json-output target/proof/proof-artifacts-check.json > target/proof/proof-artifacts-check.txt 2>&1 | |
| proof_artifacts_status=$? | |
| { | |
| echo "affected_status=${affected_status}" | |
| echo "proof_plan_status=${proof_plan_status}" | |
| echo "proof_artifacts_status=${proof_artifacts_status}" | |
| } > target/proof/status.env | |
| { | |
| echo "## Affected Proof Plan" | |
| echo "" | |
| echo "| Command | Exit |" | |
| echo "| --- | ---: |" | |
| echo "| affected | ${affected_status} |" | |
| echo "| proof plan | ${proof_plan_status} |" | |
| echo "| proof artifacts | ${proof_artifacts_status} |" | |
| echo "" | |
| echo "Affected/proof-plan artifact generation is blocking; executor command execution remains disabled, and existing CI jobs remain authoritative for executing required checks." | |
| if [ -f target/proof/proof-artifacts-check.txt ]; then | |
| echo "" | |
| cat target/proof/proof-artifacts-check.txt | |
| fi | |
| if [ -f target/proof/proof-plan.md ]; then | |
| echo "" | |
| cat target/proof/proof-plan.md | |
| fi | |
| } >> "$GITHUB_STEP_SUMMARY" | |
| if [ "${affected_status}" -ne 0 ]; then | |
| exit "${affected_status}" | |
| fi | |
| if [ "${proof_plan_status}" -ne 0 ]; then | |
| exit "${proof_plan_status}" | |
| fi | |
| if [ "${proof_artifacts_status}" -ne 0 ]; then | |
| exit "${proof_artifacts_status}" | |
| fi | |
| exit 0 | |
| - name: Upload affected proof artifacts | |
| if: always() | |
| uses: actions/upload-artifact@v7 | |
| with: | |
| name: affected-proof-plan | |
| path: target/proof/ | |
| if-no-files-found: error | |
| fast-proof-run: | |
| name: Fast Proof Run (Advisory) | |
| if: github.event_name == 'pull_request' | |
| runs-on: ubuntu-latest | |
| steps: | |
| - uses: actions/checkout@v6.0.2 | |
| - uses: dtolnay/rust-toolchain@stable | |
| with: | |
| components: rustfmt | |
| - uses: Swatinem/rust-cache@v2 | |
| with: | |
| save-if: ${{ github.ref == 'refs/heads/main' }} | |
| - id: proof_run_policy | |
| name: Resolve fast proof-run policy | |
| run: | | |
| set -euo pipefail | |
| mkdir -p target/proof-run | |
| cargo xtask proof-policy --json-output target/proof-run/proof-policy.json | |
| cargo xtask proof-run-pr-policy \ | |
| --proof-policy-json target/proof-run/proof-policy.json \ | |
| --github-output target/proof-run/proof-run-pr.outputs | |
| cat target/proof-run/proof-run-pr.outputs >> "$GITHUB_OUTPUT" | |
| - name: Run fast proof runner | |
| env: | |
| PROOF_RUN_PROFILE: ${{ steps.proof_run_policy.outputs.profile }} | |
| run: | | |
| set +e | |
| mkdir -p target/proof-run | |
| cargo xtask proof --profile "${PROOF_RUN_PROFILE}" --run-required --allow-ci-required-execution --plan-json target/proof-run/proof-plan.json --proof-run-summary target/proof-run/proof-run-summary.json | |
| proof_run_status=$? | |
| cargo xtask proof-run-artifacts-check --proof-run-summary target/proof-run/proof-run-summary.json --json-output target/proof-run/proof-run-artifacts-check.json > target/proof-run/proof-run-artifacts-check.txt 2>&1 | |
| proof_run_artifacts_status=$? | |
| cargo xtask proof-run-observation --proof-run-summary target/proof-run/proof-run-summary.json --output target/proof-run/proof-run-observation.json > target/proof-run/proof-run-observation.txt 2>&1 | |
| proof_run_observation_status=$? | |
| cargo xtask proof-workflow-status \ | |
| --workflow-kind fast-proof-run \ | |
| --status "proof_run_status=${proof_run_status}" \ | |
| --status "proof_run_artifacts_status=${proof_run_artifacts_status}" \ | |
| --status "proof_run_observation_status=${proof_run_observation_status}" \ | |
| --proof-policy target/proof-run/proof-policy.json \ | |
| --proof-plan target/proof-run/proof-plan.json \ | |
| --proof-run-summary target/proof-run/proof-run-summary.json \ | |
| --proof-run-artifacts-check target/proof-run/proof-run-artifacts-check.json \ | |
| --proof-run-observation target/proof-run/proof-run-observation.json \ | |
| --json target/proof-run/proof-workflow-status.json \ | |
| --summary-md target/proof-run/proof-workflow-status.md \ | |
| --env-output target/proof-run/proof-workflow-status.env > target/proof-run/proof-workflow-status.txt 2>&1 | |
| proof_workflow_status_status=$? | |
| if [ "${proof_workflow_status_status}" -eq 0 ]; then | |
| cargo xtask proof-workflow-status-check \ | |
| --status target/proof-run/proof-workflow-status.json \ | |
| --json target/proof-run/proof-workflow-status-check.json > target/proof-run/proof-workflow-status-check.txt 2>&1 | |
| proof_workflow_status_check_status=$? | |
| else | |
| printf 'proof-workflow-status-check skipped because proof-workflow-status exited %s\n' "${proof_workflow_status_status}" > target/proof-run/proof-workflow-status-check.txt | |
| proof_workflow_status_check_status=0 | |
| fi | |
| { | |
| echo "proof_run_status=${proof_run_status}" | |
| echo "proof_run_artifacts_status=${proof_run_artifacts_status}" | |
| echo "proof_run_observation_status=${proof_run_observation_status}" | |
| } > target/proof-run/status.env | |
| { | |
| echo "## Fast Proof Run" | |
| echo "" | |
| echo "| Command | Exit |" | |
| echo "| --- | ---: |" | |
| echo "| proof-run policy | 0 |" | |
| echo "| proof run | ${proof_run_status} |" | |
| echo "| proof run artifacts | ${proof_run_artifacts_status} |" | |
| echo "| proof run observation | ${proof_run_observation_status} |" | |
| echo "" | |
| echo "Fast proof-run artifact generation is advisory and is not part of the required CI aggregate yet." | |
| if [ -f target/proof-run/proof-run-artifacts-check.txt ]; then | |
| echo "" | |
| cat target/proof-run/proof-run-artifacts-check.txt | |
| fi | |
| if [ -f target/proof-run/proof-run-observation.txt ]; then | |
| echo "" | |
| cat target/proof-run/proof-run-observation.txt | |
| fi | |
| if [ -f target/proof-run/proof-workflow-status.txt ]; then | |
| echo "" | |
| cat target/proof-run/proof-workflow-status.txt | |
| fi | |
| if [ -f target/proof-run/proof-workflow-status-check.txt ]; then | |
| echo "" | |
| cat target/proof-run/proof-workflow-status-check.txt | |
| fi | |
| } >> "$GITHUB_STEP_SUMMARY" | |
| if [ "${proof_run_status}" -ne 0 ]; then | |
| exit "${proof_run_status}" | |
| fi | |
| if [ "${proof_run_artifacts_status}" -ne 0 ]; then | |
| exit "${proof_run_artifacts_status}" | |
| fi | |
| if [ "${proof_run_observation_status}" -ne 0 ]; then | |
| exit "${proof_run_observation_status}" | |
| fi | |
| if [ "${proof_workflow_status_status}" -ne 0 ]; then | |
| exit "${proof_workflow_status_status}" | |
| fi | |
| if [ "${proof_workflow_status_check_status}" -ne 0 ]; then | |
| exit "${proof_workflow_status_check_status}" | |
| fi | |
| exit 0 | |
| - name: Upload fast proof-run artifacts | |
| if: always() | |
| uses: actions/upload-artifact@v7 | |
| with: | |
| name: ${{ steps.proof_run_policy.outputs.artifact_name || 'fast-proof-run' }} | |
| path: target/proof-run/ | |
| if-no-files-found: error | |
| wasm-compile: | |
| name: Wasm Compile & Test | |
| needs: detect | |
| if: >- | |
| github.event_name == 'push' || | |
| contains(github.event.pull_request.labels.*.name, 'wasm') || | |
| contains(github.event.pull_request.labels.*.name, 'full-ci') || | |
| needs.detect.outputs.wasm == 'true' | |
| runs-on: ubuntu-latest | |
| steps: | |
| - uses: actions/checkout@v6.0.2 | |
| - uses: dtolnay/rust-toolchain@stable | |
| with: | |
| targets: wasm32-unknown-unknown | |
| - uses: actions/setup-node@v6 | |
| - uses: taiki-e/install-action@v2 | |
| with: | |
| tool: wasm-pack | |
| - uses: Swatinem/rust-cache@v2 | |
| with: | |
| save-if: ${{ github.ref == 'refs/heads/main' }} | |
| - name: Check tokmd-scan for wasm32 | |
| run: cargo check -p tokmd-scan --target wasm32-unknown-unknown | |
| - name: Check tokmd-core analysis for wasm32 | |
| run: cargo check -p tokmd-core --features analysis --target wasm32-unknown-unknown | |
| - name: Check tokmd-wasm core-only for wasm32 | |
| run: cargo check -p tokmd-wasm --no-default-features --target wasm32-unknown-unknown | |
| - name: Check tokmd-wasm for wasm32 | |
| run: cargo check -p tokmd-wasm --features analysis --target wasm32-unknown-unknown | |
| - name: Run tokmd-wasm core-only wasm tests | |
| run: wasm-pack test --node crates/tokmd-wasm --no-default-features | |
| - name: Run tokmd-wasm analysis wasm tests | |
| run: wasm-pack test --node crates/tokmd-wasm --features analysis | |
| - name: Build browser runner wasm bundle | |
| run: npm --prefix web/runner run build:wasm | |
| - name: Browser runner protocol and wasm boot smoke | |
| run: npm --prefix web/runner test | |
| - name: Browser runner syntax check | |
| run: npm --prefix web/runner run check | |
| gate: | |
| name: Quality Gate | |
| runs-on: ubuntu-latest | |
| steps: | |
| - uses: actions/checkout@v6.0.2 | |
| - uses: dtolnay/rust-toolchain@stable | |
| with: | |
| components: clippy, rustfmt | |
| - name: Use larger writable target dir on Linux | |
| if: runner.os == 'Linux' | |
| run: echo "CARGO_TARGET_DIR=${RUNNER_TEMP}/target" >> "$GITHUB_ENV" | |
| - uses: Swatinem/rust-cache@v2 | |
| with: | |
| save-if: ${{ github.ref == 'refs/heads/main' }} | |
| - name: Run quality gate | |
| run: cargo xtask gate --check | |
| deny: | |
| name: Cargo Deny | |
| runs-on: ubuntu-latest | |
| steps: | |
| - uses: actions/checkout@v6.0.2 | |
| - uses: taiki-e/install-action@v2 | |
| with: | |
| tool: cargo-deny@0.19.0 | |
| - name: Print cargo-deny version | |
| run: cargo deny --version | |
| - name: Check advisories and licenses | |
| run: cargo deny --all-features check | |
| typos: | |
| name: Typos | |
| runs-on: ubuntu-latest | |
| steps: | |
| - uses: actions/checkout@v6.0.2 | |
| - uses: crate-ci/typos@v1 | |
| proptest-smoke: | |
| name: Proptest Smoke | |
| needs: detect | |
| if: >- | |
| github.event_name == 'push' || | |
| contains(github.event.pull_request.labels.*.name, 'property-tests') || | |
| contains(github.event.pull_request.labels.*.name, 'full-ci') || | |
| needs.detect.outputs.core_receipts == 'true' || | |
| needs.detect.outputs.analysis == 'true' | |
| runs-on: ubuntu-latest | |
| steps: | |
| - uses: actions/checkout@v6.0.2 | |
| - uses: dtolnay/rust-toolchain@stable | |
| - uses: Swatinem/rust-cache@v2 | |
| with: | |
| save-if: ${{ github.ref == 'refs/heads/main' }} | |
| - name: Run property tests (reduced iterations) | |
| run: | | |
| PROPTEST_CASES=50 cargo test -p tokmd-model --test properties --all-features --verbose | |
| PROPTEST_CASES=50 cargo test -p tokmd-types --test properties --all-features --verbose | |
| PROPTEST_CASES=50 cargo test -p tokmd --test properties --all-features --verbose | |
| env: | |
| CI: true | |
| publish-plan: | |
| name: Publish Surface | |
| runs-on: ubuntu-latest | |
| steps: | |
| - uses: actions/checkout@v6.0.2 | |
| - uses: dtolnay/rust-toolchain@stable | |
| - uses: Swatinem/rust-cache@v2 | |
| with: | |
| save-if: ${{ github.ref == 'refs/heads/main' }} | |
| - name: Verify publish surface and dry-run checks | |
| run: cargo xtask publish-surface --json --verify-publish | |
| env: | |
| CI: true | |
| version-consistency: | |
| name: Version consistency | |
| runs-on: ubuntu-latest | |
| steps: | |
| - uses: actions/checkout@v6.0.2 | |
| - uses: dtolnay/rust-toolchain@stable | |
| - name: Check release metadata alignment | |
| run: cargo xtask version-consistency | |
| docs-check: | |
| name: Docs Check | |
| runs-on: ubuntu-latest | |
| steps: | |
| - uses: actions/checkout@v6.0.2 | |
| - uses: dtolnay/rust-toolchain@stable | |
| - uses: Swatinem/rust-cache@v2 | |
| with: | |
| save-if: ${{ github.ref == 'refs/heads/main' }} | |
| - name: Check documentation drift | |
| run: | | |
| set +e | |
| mkdir -p target/docs | |
| cargo xtask doc-artifacts --check --json target/docs/doc-artifacts-check.json | |
| doc_artifacts_status=$? | |
| cargo xtask docs --check | |
| docs_status=$? | |
| if [ -f target/docs/doc-artifacts-check.json ]; then | |
| { | |
| echo "## Documentation Artifact Check" | |
| echo "" | |
| echo '```json' | |
| cat target/docs/doc-artifacts-check.json | |
| echo '```' | |
| } >> "$GITHUB_STEP_SUMMARY" | |
| fi | |
| if [ "${doc_artifacts_status}" -ne 0 ]; then | |
| exit "${doc_artifacts_status}" | |
| fi | |
| if [ "${docs_status}" -ne 0 ]; then | |
| exit "${docs_status}" | |
| fi | |
| exit 0 | |
| env: | |
| CI: true | |
| - name: Upload doc artifact check receipt | |
| if: always() | |
| uses: actions/upload-artifact@v7 | |
| with: | |
| name: doc-artifacts-check | |
| path: target/docs/doc-artifacts-check.json | |
| if-no-files-found: warn | |
| nix-pr: | |
| name: Nix PR Package Gate | |
| needs: detect | |
| # Nix package validation is publication-owned. Keep it out of the | |
| # tokmd-swarm workbench so swarm push/PR checks do not depend on | |
| # publication repo Nix state. | |
| if: >- | |
| github.repository == 'EffortlessMetrics/tokmd' && | |
| (github.event_name == 'push' || | |
| contains(github.event.pull_request.labels.*.name, 'nix') || | |
| contains(github.event.pull_request.labels.*.name, 'release-check') || | |
| contains(github.event.pull_request.labels.*.name, 'full-ci') || | |
| needs.detect.outputs.nix == 'true' || | |
| needs.detect.outputs.release == 'true') | |
| runs-on: ubuntu-latest | |
| timeout-minutes: 60 | |
| permissions: | |
| contents: read | |
| env: | |
| FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: true | |
| steps: | |
| - uses: actions/checkout@v6.0.2 | |
| - name: Install Nix | |
| uses: DeterminateSystems/nix-installer-action@v22 | |
| - name: Setup Nix cache | |
| continue-on-error: true | |
| uses: DeterminateSystems/magic-nix-cache-action@v14 | |
| with: | |
| use-flakehub: false | |
| use-gha-cache: enabled | |
| - name: Check flake | |
| run: nix flake check --no-build --accept-flake-config | |
| - name: Build package | |
| run: nix build -L .#tokmd | |
| mutation: | |
| name: Mutation Testing | |
| runs-on: ubuntu-latest | |
| # Runtime mutation testing moves to label/push-to-main so ordinary | |
| # PRs don't pay 45 LEM by default. PR 11 adds an advisory `ripr` | |
| # job that gives mutation-testing-lite signal at static-analysis | |
| # prices for the default PR. The "(Required)" suffix is dropped to | |
| # avoid implying this lane is a mandatory PR check; required-status | |
| # guarantees live on the `CI (Required)` aggregator job. | |
| if: >- | |
| github.event_name == 'push' || | |
| contains(github.event.pull_request.labels.*.name, 'mutation') || | |
| contains(github.event.pull_request.labels.*.name, 'full-ci') | |
| steps: | |
| - uses: actions/checkout@v6.0.2 | |
| with: | |
| fetch-depth: 0 | |
| - uses: dtolnay/rust-toolchain@stable | |
| - name: Install cargo-mutants | |
| run: cargo install cargo-mutants --version 26.1.2 --locked | |
| - name: Get changed Rust files | |
| id: changed | |
| env: | |
| PUSH_BEFORE: ${{ github.event.before || '' }} | |
| run: | | |
| set -euo pipefail | |
| if [ "${{ github.event_name }}" = "pull_request" ]; then | |
| BASE_REF="${{ github.base_ref }}" | |
| BASE_REV="origin/$BASE_REF" | |
| git fetch origin "$BASE_REF":"refs/remotes/origin/$BASE_REF" || true | |
| elif [ -n "${PUSH_BEFORE}" ] && ! printf '%s' "${PUSH_BEFORE}" | grep -Eq '^0+$'; then | |
| BASE_REF="main" | |
| BASE_REV="${PUSH_BEFORE}" | |
| elif git rev-parse --verify HEAD^ >/dev/null 2>&1; then | |
| BASE_REF="main" | |
| BASE_REV="HEAD^" | |
| else | |
| BASE_REF="main" | |
| BASE_REV="HEAD" | |
| fi | |
| cargo xtask mutation-scope \ | |
| --base-ref "$BASE_REF" \ | |
| --base "$BASE_REV" \ | |
| --head HEAD \ | |
| --max-files 20 \ | |
| --all-changed-files all_changed_files.txt \ | |
| --changed-files changed_files.txt \ | |
| --json-output target/mutation/mutation-scope.json \ | |
| --github-output "$GITHUB_OUTPUT" | |
| - name: Mutation Testing | |
| if: steps.changed.outputs.count != '0' | |
| env: | |
| CI: true | |
| run: | | |
| set -euo pipefail | |
| echo "Running mutation testing on ${{ steps.changed.outputs.count }} changed file(s)..." | |
| while IFS= read -r file; do | |
| [ -z "$file" ] && continue | |
| [ -f "$file" ] || continue | |
| # Microcrate dividend: run from crate dir when possible | |
| crate_dir="" | |
| if [[ "$file" == crates/*/* ]]; then | |
| crate_dir="$(echo "$file" | cut -d/ -f1-2)" | |
| fi | |
| echo "::group::Mutants: $file" | |
| if [[ -n "$crate_dir" && -f "$crate_dir/Cargo.toml" ]]; then | |
| rel="${file#$crate_dir/}" | |
| (cd "$crate_dir" && cargo mutants --file "$rel" --timeout 300) | |
| else | |
| cargo mutants --file "$file" --timeout 300 | |
| fi | |
| echo "::endgroup::" | |
| done < changed_files.txt | |
| echo "✓ All mutations caught or unviable (0 MISSED)" | |
| - name: Skip notice | |
| if: steps.changed.outputs.count == '0' && steps.changed.outputs.scope_exceeded != 'true' | |
| run: echo "No production Rust files changed - skipping mutation testing" | |
| - name: Scope exceeded notice | |
| if: steps.changed.outputs.scope_exceeded == 'true' | |
| run: echo "Mutation scope exceeded - selected files were cleared. See target/mutation/mutation-scope.json." | |
| - name: Upload mutants report | |
| if: always() | |
| uses: actions/upload-artifact@v7 | |
| with: | |
| name: mutants-report | |
| path: | | |
| target/mutation/mutation-scope.json | |
| all_changed_files.txt | |
| changed_files.txt | |
| mutants.out/ | |
| crates/**/mutants.out/ | |
| if-no-files-found: ignore | |
| retention-days: 14 | |
| ci-required: | |
| name: CI (Required) | |
| permissions: | |
| contents: read | |
| actions: read | |
| runs-on: ubuntu-latest | |
| if: always() | |
| needs: | |
| - detect | |
| - msrv | |
| - build | |
| - build-windows | |
| - build-macos | |
| - gate | |
| - deny | |
| - wasm-compile | |
| - typos | |
| - proptest-smoke | |
| - publish-plan | |
| - version-consistency | |
| - docs-check | |
| - nix-pr | |
| - mutation | |
| - feature-boundaries | |
| - proof-policy | |
| steps: | |
| - uses: actions/checkout@v6.0.2 | |
| continue-on-error: true | |
| - uses: dtolnay/rust-toolchain@stable | |
| continue-on-error: true | |
| - uses: Swatinem/rust-cache@v2 | |
| continue-on-error: true | |
| with: | |
| save-if: ${{ github.ref == 'refs/heads/main' }} | |
| - name: Generate CI timings sidecar | |
| if: always() | |
| continue-on-error: true | |
| env: | |
| GITHUB_TOKEN: ${{ github.token }} | |
| run: | | |
| set -euo pipefail | |
| mkdir -p target/ci | |
| python - <<'PY' | |
| import json | |
| import os | |
| import urllib.request | |
| from datetime import datetime | |
| token = os.environ.get("GITHUB_TOKEN", "") | |
| repo = os.environ["GITHUB_REPOSITORY"] | |
| run_id = os.environ["GITHUB_RUN_ID"] | |
| run_attempt = os.environ.get("GITHUB_RUN_ATTEMPT", "1") | |
| headers = { | |
| "Accept": "application/vnd.github+json", | |
| "X-GitHub-Api-Version": "2022-11-28", | |
| } | |
| if token: | |
| headers["Authorization"] = f"Bearer {token}" | |
| job_name_to_need_key = { | |
| "Detect risk packs": "detect", | |
| "MSRV Check": "msrv", | |
| "Build & Test (Linux)": "build", | |
| "Build & Test (Windows)": "build-windows", | |
| "Build & Test (macOS)": "build-macos", | |
| "Quality Gate": "gate", | |
| "Cargo Deny": "deny", | |
| "Wasm Compile & Test": "wasm-compile", | |
| "Typos": "typos", | |
| "Proptest Smoke": "proptest-smoke", | |
| "Publish Surface": "publish-plan", | |
| "Version consistency": "version-consistency", | |
| "Docs Check": "docs-check", | |
| "Nix PR Package Gate": "nix-pr", | |
| "Mutation Testing": "mutation", | |
| "Feature Boundaries": "feature-boundaries", | |
| "Proof Policy": "proof-policy", | |
| } | |
| def parse_time(value): | |
| if not value: | |
| return None | |
| return datetime.fromisoformat(value.replace("Z", "+00:00")) | |
| timings = {} | |
| page = 1 | |
| while True: | |
| url = ( | |
| f"https://api.github.com/repos/{repo}/actions/runs/{run_id}" | |
| f"/attempts/{run_attempt}/jobs?per_page=100&page={page}" | |
| ) | |
| req = urllib.request.Request(url, headers=headers) | |
| with urllib.request.urlopen(req, timeout=20) as response: | |
| payload = json.load(response) | |
| jobs = payload.get("jobs", []) | |
| for job in jobs: | |
| if job.get("conclusion") != "success": | |
| continue | |
| need_key = job_name_to_need_key.get(job.get("name")) | |
| started = parse_time(job.get("started_at")) | |
| completed = parse_time(job.get("completed_at")) | |
| if not need_key or not started or not completed: | |
| continue | |
| duration_seconds = max(0.0, (completed - started).total_seconds()) | |
| record = {"duration_seconds": duration_seconds} | |
| labels = job.get("labels") or [] | |
| runner = next((label for label in labels if isinstance(label, str) and label), None) | |
| if runner: | |
| record["runner"] = runner | |
| timings[need_key] = record | |
| if len(jobs) < 100: | |
| break | |
| page += 1 | |
| with open("target/ci/timings.json", "w", encoding="utf-8") as handle: | |
| json.dump(timings, handle, indent=2, sort_keys=True) | |
| handle.write("\n") | |
| print(f"CI timings sidecar written with {len(timings)} job(s)") | |
| PY | |
| - name: Generate CI actuals receipt | |
| if: always() | |
| continue-on-error: true | |
| env: | |
| NEEDS_JSON: ${{ toJson(needs) }} | |
| run: | | |
| set -euo pipefail | |
| mkdir -p target/ci | |
| printf '%s\n' "${NEEDS_JSON}" > target/ci/needs.json | |
| timing_args=() | |
| if [ -s target/ci/timings.json ]; then | |
| timing_args=(--timings target/ci/timings.json) | |
| fi | |
| cargo xtask ci-actuals \ | |
| --needs target/ci/needs.json \ | |
| "${timing_args[@]}" \ | |
| --output target/ci/ci-actuals.json \ | |
| --github-summary "$GITHUB_STEP_SUMMARY" \ | |
| --sha "${GITHUB_SHA}" | |
| - name: Upload CI actuals receipt | |
| if: always() | |
| continue-on-error: true | |
| uses: actions/upload-artifact@v7 | |
| with: | |
| name: ci-actuals | |
| path: | | |
| target/ci/needs.json | |
| target/ci/timings.json | |
| target/ci/ci-actuals.json | |
| if-no-files-found: warn | |
| retention-days: 14 | |
| - name: Check overall status | |
| if: always() | |
| env: | |
| NEEDS_JSON: ${{ toJson(needs) }} | |
| run: | | |
| python - <<'PY' | |
| import json | |
| import os | |
| import sys | |
| needs = json.loads(os.environ["NEEDS_JSON"]) | |
| failed = [] | |
| cancelled = [] | |
| lines = [ | |
| "## Required CI job summary", | |
| "", | |
| "| Job | Result |", | |
| "| --- | --- |", | |
| ] | |
| for job, payload in sorted(needs.items()): | |
| result = payload.get("result", "unknown") | |
| lines.append(f"| `{job}` | `{result}` |") | |
| if result == "failure": | |
| failed.append(job) | |
| elif result == "cancelled": | |
| cancelled.append(job) | |
| summary = "\n".join(lines) + "\n" | |
| summary_path = os.environ.get("GITHUB_STEP_SUMMARY") | |
| if summary_path: | |
| with open(summary_path, "a", encoding="utf-8") as handle: | |
| handle.write(summary) | |
| print(summary) | |
| if failed or cancelled: | |
| if failed: | |
| print(f"::error::Failed jobs: {', '.join(failed)}") | |
| if cancelled: | |
| print(f"::error::Cancelled jobs: {', '.join(cancelled)}") | |
| sys.exit(1) | |
| print("All required jobs passed (or were skipped).") | |
| PY |