Skip to content

badge: refresh public endpoints #5779

badge: refresh public endpoints

badge: refresh public endpoints #5779

Workflow file for this run

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