Skip to content
Closed
2 changes: 2 additions & 0 deletions .github/actionlint.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -30,3 +30,5 @@ self-hosted-runner:
- review-light
# #5254: the 8 GB ARM64 CUDA edge device's LAB nightly (edge-fast-verbs-nightly)
- jetson
# #5889: the Kubuntu Focus host, kfocus-shadow-lab (dispatch-only LAB)
- kfocus
4 changes: 4 additions & 0 deletions .github/workflows/assets-rehearsal-nightly.yml
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,7 @@ jobs:
name: pv ${{ matrix.target }} on ${{ matrix.host }}
needs: assets
runs-on: ${{ fromJSON(matrix.labels) }}
timeout-minutes: 120
strategy:
fail-fast: false
matrix:
Expand Down Expand Up @@ -393,6 +394,7 @@ jobs:
name: apr (cuda) ${{ matrix.target }} on ${{ matrix.host }}
needs: assets
runs-on: ${{ fromJSON(matrix.labels) }}
timeout-minutes: 120
strategy:
fail-fast: false
matrix:
Expand Down Expand Up @@ -545,6 +547,7 @@ jobs:
name: apr (cpu) ${{ matrix.target }} on ${{ matrix.host }}
needs: assets
runs-on: ${{ fromJSON(matrix.labels) }}
timeout-minutes: 120
strategy:
fail-fast: false
matrix:
Expand Down Expand Up @@ -710,6 +713,7 @@ jobs:
name: apr (cpu) aarch64-apple-darwin on mini
needs: assets
runs-on: [self-hosted, macOS, ARM64, mini-builder]
timeout-minutes: 120
env:
MACOSX_DEPLOYMENT_TARGET: "11.0"
steps:
Expand Down
6 changes: 6 additions & 0 deletions .github/workflows/book-contracts.yml
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,7 @@ jobs:
chapter-examples-compile:
name: Chapter Examples Compile
runs-on: [self-hosted, Linux, X64, clean-room]
timeout-minutes: 60
steps:
- uses: actions/checkout@v7
- uses: dtolnay/rust-toolchain@stable
Expand Down Expand Up @@ -103,6 +104,7 @@ jobs:
chapter-examples-run:
name: Chapter Examples Run
runs-on: [self-hosted, Linux, X64, clean-room]
timeout-minutes: 60
needs: chapter-examples-compile
steps:
- uses: actions/checkout@v7
Expand Down Expand Up @@ -155,6 +157,7 @@ jobs:
book-integration-tests:
name: Book Integration Tests
runs-on: [self-hosted, Linux, X64, clean-room]
timeout-minutes: 60
steps:
- uses: actions/checkout@v7
- uses: dtolnay/rust-toolchain@stable
Expand Down Expand Up @@ -187,6 +190,7 @@ jobs:
contract-enforcement:
name: Contract Enforcement
runs-on: [self-hosted, Linux, X64, clean-room]
timeout-minutes: 60
steps:
- uses: actions/checkout@v7
- name: Every chapter has a contract
Expand Down Expand Up @@ -247,6 +251,7 @@ jobs:
namespace-discipline:
name: Namespace Discipline
runs-on: [self-hosted, Linux, X64, clean-room]
timeout-minutes: 60
steps:
- uses: actions/checkout@v7
- name: No legacy imports in chapter examples
Expand Down Expand Up @@ -288,6 +293,7 @@ jobs:
summary-integrity:
name: SUMMARY.md Integrity
runs-on: [self-hosted, Linux, X64, clean-room]
timeout-minutes: 60
steps:
- uses: actions/checkout@v7
- name: No dead links in SUMMARY.md
Expand Down
2 changes: 2 additions & 0 deletions .github/workflows/book.yml
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,7 @@ jobs:
build:
name: Build Book
runs-on: [self-hosted, Linux, X64, clean-room]
timeout-minutes: 120
steps:
- uses: actions/checkout@v7

Expand Down Expand Up @@ -206,6 +207,7 @@ jobs:
deploy:
name: Deploy to GitHub Pages
runs-on: [self-hosted, Linux, X64, clean-room]
timeout-minutes: 60
needs: build
if: github.event_name == 'push' && github.ref == 'refs/heads/main'
# GitHub Pages deploys never overlap and are never cancelled (was workflow-level).
Expand Down
27 changes: 23 additions & 4 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -198,7 +198,7 @@ jobs:
if: ${{ !cancelled() && needs.mg-reuse.outputs.x86 != '1' && github.event.pull_request.draft != true }}
# pull_request/push stay on the shared clean-room pool; a merge_group entry takes the mq lane (custom label exactly mq)
runs-on: ${{ github.event_name == 'merge_group' && fromJSON('["self-hosted","Linux","X64","mq"]') || fromJSON('["self-hosted","Linux","X64","clean-room"]') }}
timeout-minutes: 240
timeout-minutes: ${{ github.event_name == 'merge_group' && 75 || 240 }}
# The sections carry their own permissions blocks (the sov provenance section
# needs id-token + attestations); one job token must cover all of them.
permissions: write-all
Expand Down Expand Up @@ -304,6 +304,14 @@ jobs:
# the teardown below keeps always() and still reaps the containers.
if: ${{ !cancelled() }}
run: python3 scripts/ci/fat_driver.py wait --results "$RUNNER_TEMP/fat/results.json"
# Operator 2026-10-19: root cause is no gate time budget. A merge_group entry that is already RED must
# not keep the other jobs and shards holding mq runners for another ~45 min: the first failed job cancels
# the run. merge_group only (a PR run keeps every verdict); the teardown below is always() and still reaps.
- name: Fail fast, cancel this merge-group run
if: ${{ failure() && github.event_name == 'merge_group' }}
env:
GH_TOKEN: ${{ github.token }}
run: gh run cancel "$GITHUB_RUN_ID" --repo "$GITHUB_REPOSITORY" || echo "::warning::fail-fast cancel did not take"
# #4948: a cancelled run never reaches a section's own container removal,
# and its fat-<run>-<section> container then sits on `tail -f /dev/null`
# for days. always() runs on cancel too; on the passing path the driver
Expand Down Expand Up @@ -530,12 +538,15 @@ jobs:
if: ${{ !cancelled() && needs.mg-reuse-wt.outputs.wt != '1' && github.event.pull_request.draft != true }}
# same lanes as x86-main: clean-room for pull_request/push, mq for a merge_group entry
runs-on: ${{ github.event_name == 'merge_group' && fromJSON('["self-hosted","Linux","X64","mq"]') || fromJSON('["self-hosted","Linux","X64","clean-room"]') }}
timeout-minutes: 180
timeout-minutes: ${{ github.event_name == 'merge_group' && 75 || 180 }}
permissions:
contents: read
actions: write # the fail-fast step cancels this run
strategy:
fail-fast: false
matrix:
shard: ${{ fromJSON(github.event_name == 'merge_group' && '[1,2,3,4]' || '[1,2,3,4,5,6,7,8,9,10,11,12]') }}
shards: ${{ fromJSON(github.event_name == 'merge_group' && '[4]' || '[12]') }}
shard: ${{ fromJSON(github.event_name == 'merge_group' && '[1]' || '[1]') }}
shards: ${{ fromJSON(github.event_name == 'merge_group' && '[1]' || '[1]') }}
outputs:
results: ${{ steps.run.outputs.results }}
env:
Expand All @@ -555,6 +566,14 @@ jobs:
python3 scripts/ci/fat_driver.py run
--sections 'workspace-test-shard?${{ matrix.shard }}?${{ matrix.shards }}?'
--results "$RUNNER_TEMP/fat/results.json"
# Operator 2026-10-19: root cause is no gate time budget. A merge_group entry that is already RED must
# not keep the other jobs and shards holding mq runners for another ~45 min: the first failed job cancels
# the run. merge_group only (a PR run keeps every verdict); the teardown below is always() and still reaps.
- name: Fail fast, cancel this merge-group run
if: ${{ failure() && github.event_name == 'merge_group' }}
env:
GH_TOKEN: ${{ github.token }}
run: gh run cancel "$GITHUB_RUN_ID" --repo "$GITHUB_REPOSITORY" || echo "::warning::fail-fast cancel did not take"
# #4948: a cancelled run never reaches a section's own container removal,
# and its fat-<run>-<section> container then sits on `tail -f /dev/null`
# for days. always() runs on cancel too; on the passing path the driver
Expand Down
4 changes: 2 additions & 2 deletions .github/workflows/dogfood-nightly.yml
Original file line number Diff line number Diff line change
Expand Up @@ -24,11 +24,11 @@ name: dogfood-nightly

on:
schedule:
# 23:05 UTC: five minutes after nightly.yml's 23:00, so both land in the same late dispatch.
# 20:05 UTC: five minutes after nightly.yml's 20:00 (night 75 + dogfood 420 ends 04:20Z), so both land in the same late dispatch.
# The train judges the head of THAT night's scheduled nightly.yml run (C); the `night` job
# below resolves C and the dogfood job checks it out, so a merge after 21:00 no longer
# leaves the lane measuring some other commit (#5079).
- cron: '5 23 * * *'
- cron: '5 20 * * *'
workflow_dispatch:
inputs:
plant:
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/edge-fast-verbs-nightly.yml
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ name: Edge fast-verbs LAB nightly

on:
schedule:
- cron: '30 2 * * *'
- cron: '30 0 * * *'
workflow_dispatch:
inputs:
model:
Expand Down
4 changes: 2 additions & 2 deletions .github/workflows/fleet-toolset.yml
Original file line number Diff line number Diff line change
Expand Up @@ -41,10 +41,10 @@ name: Fleet Toolset

on:
schedule:
# 04:47 UTC — an off-minute, and clear of cuda-nightly (01:30) and
# 04:35 UTC — an off-minute, and clear of cuda-nightly (01:30) and
# silicon-nightly (03:30) so the probe never queues behind a 90-minute
# measurement it would otherwise appear to have slowed down.
- cron: "47 4 * * *"
- cron: "35 4 * * *"
workflow_dispatch:

permissions:
Expand Down
1 change: 1 addition & 0 deletions .github/workflows/install-script.yml
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@ jobs:
test:
name: install_test.sh
runs-on: [self-hosted, Linux, X64, clean-room]
timeout-minutes: 60
steps:
- uses: actions/checkout@v7

Expand Down
179 changes: 179 additions & 0 deletions .github/workflows/kfocus-shadow-lab.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,179 @@
# kfocus shadow parity vs intel -- LAB (C324), dispatch only.
#
# Operator ruling 2026-10-21 17:15Z: run the SAME x86-main job as ci.yml on the kfocus
# host and compare the verdict with intel's. This is a LAB check: it never runs inside
# a PR, merge-queue or release job, so the only trigger is workflow_dispatch. Do not add
# pull_request, push, merge_group or schedule here.
#
# FULL TIER, never reuse (the 13:35Z parity run was "all tier=reuse, so no tests ran"):
# the tier is decided inside the guard-cargo/sov sections by ci/sections.yml's
# "Decide the test tier" step -> scripts/ci_test_tier.sh, which answers tier=full for
# event workflow_dispatch (scripts/ci_test_tier.sh, the `schedule|workflow_dispatch` arm).
# Reuse needs a `push` event plus a merge_group result and the mg-reuse job, which this
# workflow does not have. The "Pin the tier" step below asserts that arm still says full,
# so a future edit that gives dispatch a quick/reuse tier turns this lane red instead of
# silently running nothing.
name: kfocus-shadow-lab

on:
workflow_dispatch:
inputs:
sha:
description: Commit to test (full 40-char sha)
required: true
type: string

permissions:
contents: read

concurrency:
group: kfocus-shadow-${{ inputs.sha }}
cancel-in-progress: false

jobs:
x86-main-shadow:
runs-on: [self-hosted, Linux, X64, kfocus]
timeout-minutes: 240
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
FAT_VARS_JSON: ${{ toJSON(vars) }}
FAT_SECRET_PR_REVIEW_SIGNING_KEY_B64: ${{ secrets.PR_REVIEW_SIGNING_KEY_B64 }}
steps:
# The fleet pins jq 1.8.2. Runners in this clean-room pool whose image ships jq 1.6
# print a number literal such as 90.00 as 90, so the guard-tree section turns RED
# (scripts/release/tag_coverage_gate.sh --self-test, run by check_tag_coverage_gated.sh).
# This step puts the pinned jq first on PATH for every later step, and through it for
# every section fat_driver runs on the host (step_env copies the driver's PATH; a
# section with a job container gets the image's PATH, and none of those calls jq).
# Fail closed: a checksum, version or behaviour mismatch stops the job. The sha256 is
# the one in the upstream release's sha256sum.txt for jq-linux-amd64.
- name: Pin jq 1.8.2 (sha256-verified, fail closed)
env:
JQ_URL: https://github.com/jqlang/jq/releases/download/jq-1.8.2/jq-linux-amd64
JQ_SHA256: b1c22172dd303f3be49e935aa56aa48a8b7a46e0bc838b4997d3bb451495870f
run: |
set -euo pipefail
d="$RUNNER_TEMP/jq-1.8.2"
mkdir -p "$d"
curl -fsSL --retry 3 --retry-delay 5 -o "$d/jq" "$JQ_URL"
printf '%s %s\n' "$JQ_SHA256" "$d/jq" | sha256sum -c -
chmod +x "$d/jq"
v=$("$d/jq" --version)
[ "$v" = "jq-1.8.2" ] || { echo "::error::the pinned jq reports '$v', not jq-1.8.2"; exit 1; }
rc=0; "$d/jq" -e . < /dev/null > /dev/null || rc=$?
[ "$rc" = 4 ] || { echo "::error::the pinned jq exits $rc on empty input with -e, not 4"; exit 1; }
echo "$d" >> "$GITHUB_PATH"
- name: jq on PATH is the pinned one
run: |
set -euo pipefail
command -v jq; jq --version
[ "$(command -v jq)" = "$RUNNER_TEMP/jq-1.8.2/jq" ] || { echo "::error::jq on PATH is $(command -v jq), not the pinned one"; exit 1; }
[ "$(jq --version)" = "jq-1.8.2" ] || { echo "::error::jq on PATH reports $(jq --version)"; exit 1; }
# Full history: a section whose own checkout asked for depth 1 (the default)
# is cut back to exactly that by the driver; one asking for 0 needs it all.
- uses: actions/checkout@v7
with:
ref: ${{ inputs.sha }}
fetch-depth: 0
# Fail fast: the reds car#4429 took four fat runs to see, in ~2 min, before
# any section starts. rustfmt runs in the image sov.lint uses (same digest,
# so the same verdict); the ID-order rule is contract_data_integrity's,
# without cargo; the case tables are guard-tree's cheapest.
- name: Cheap checks (fmt, falsification-ID order, guard case tables)
run: |
set -euo pipefail
python3 scripts/ci/fail_fast.py self-test
python3 scripts/ci/fail_fast.py ids --base origin/main
docker run --rm -v "$GITHUB_WORKSPACE:/w:ro" -w /w \
localhost:5000/sovereign-ci:stable@sha256:70f2cdc9e537fe4447306d33c317fea33017f78f1ffa3b7af99095391419ef36 \
cargo fmt --all -- --check
bash scripts/tests/guard_tree_test.sh
bash scripts/bump-version.sh --self-test
setsid --wait bash scripts/check_no_claim_literals.sh --selftest
setsid --wait bash scripts/check_hardcoded_paths.sh --self-test
setsid --wait bash scripts/check_pr_review_wiring.sh --self-test
setsid --wait bash scripts/ci/fixture_quiet.sh --self-test
setsid --wait bash scripts/ci/fetch_p6.sh --self-test
setsid --wait bash scripts/ci/fetch_p6.sh --check-wiring
# The driver decides what every section's if:/matrix/needs means and whether
# the vendored workflow is the upstream one; its case table must go RED on
# each planted defect before it is trusted to report a section green.
# #4596: its fixture rows print real ::error:: lines; fixture_quiet.sh shows
# them as labelled text, never as job annotations, and keeps the exit status.
- name: fat_driver case table must still turn RED
run: setsid --wait bash scripts/ci/fixture_quiet.sh "fat_driver self-test" -- python3 scripts/ci/fat_driver.py self-test
# PMAT-4510: every secrets.<NAME> a section reads must be plumbed above as
# FAT_SECRET_<NAME> under its full name; #4441 cut one short and the
# receipt signer ran with an empty key on every PR. Text-only, no build.
- name: FAT_SECRET plumbing guard case table
run: setsid --wait bash scripts/check_ci_fat_secrets_plumbed.sh --self-test
- name: every section secret is plumbed under its full name
run: setsid --wait bash scripts/check_ci_fat_secrets_plumbed.sh
# #5403: the toolchain step installs `stable`; guard-cargo#m60 runs cargo without
# RUSTUP_TOOLCHAIN, so cargo read rust-toolchain.toml and downloaded the pinned
# release at test time (one network blip failed a merge group). Install the pin first.
- name: install the toolchain rust-toolchain.toml pins (case table)
run: setsid --wait bash scripts/ci/install_pinned_toolchain.sh --self-test
- name: install the toolchain rust-toolchain.toml pins
run: setsid --wait bash scripts/ci/install_pinned_toolchain.sh
# LAB: pin the FULL tier. The dispatch arm of ci_test_tier.sh must say full (never reuse, never quick).
- name: Pin the tier to full (dispatch must never reuse or skip)
run: |
set -euo pipefail
out=$(bash scripts/ci_test_tier.sh --event workflow_dispatch)
printf "%s\n" "$out"
printf "%s\n" "$out" | grep -qx "tier=full" || { echo "::error::workflow_dispatch no longer yields tier=full; this shadow run would test nothing"; exit 1; }
- name: Sections (the determinism raster returns first; the rest keep running)
run: >-
python3 scripts/ci/fat_driver.py run
--sections 'sov.*,guard-tree,guard-cargo,vendored-schemas,pr-review-shadow,pr-review-sign,determinism[X64]'
--results "$RUNNER_TEMP/fat/results.json"
--background-until 'determinism[X64]'
# Published mid-run: the determinism job's compare section is waiting for it.
- uses: actions/upload-artifact@v7
with:
name: determinism-X64
path: ${{ runner.temp }}/fat/artifacts/determinism-X64/
if-no-files-found: error
- name: Sections (wait for every one to finish)
id: wait
# A cancel must stop this poll (orphan queue runs held runners, 2026-10-16);
# the teardown below keeps always() and still reaps the containers.
if: ${{ !cancelled() }}
run: python3 scripts/ci/fat_driver.py wait --results "$RUNNER_TEMP/fat/results.json"
# #4948: a cancelled run never reaches a section's own container removal,
# and its fat-<run>-<section> container then sits on `tail -f /dev/null`
# for days. always() runs on cancel too; on the passing path the driver
# has already removed every container and this finds none. Cancelled
# before the checkout, no driver ran and there is no script to call.
- name: Remove this job's section containers (runs on cancel too)
if: always()
run: if [ -f scripts/ci/fat_teardown.sh ]; then bash scripts/ci/fat_teardown.sh "$GITHUB_RUN_ID" "$RUNNER_TEMP"; fi
- uses: actions/upload-artifact@v7
if: always()
with:
name: pr-review-shadow-sample
path: ${{ runner.temp }}/fat/artifacts/pr-review-shadow-sample/
retention-days: 90
if-no-files-found: ignore
- uses: actions/upload-artifact@v7
if: always()
with:
name: bench-${{ github.event.repository.name }}
path: ${{ runner.temp }}/fat/artifacts/bench-${{ github.event.repository.name }}/
retention-days: 90
if-no-files-found: ignore
# #5397: free this job's own target dir (`<PR>/run-<id>`), never the PR parent.
# Last step, so nothing after it reads the dir; always() so a failed or
# cancelled run frees it too.
- name: Free this job's own cargo target dir
if: always()
run: if [ -f scripts/ci/free_own_target_dir.sh ]; then bash scripts/ci/free_own_target_dir.sh "${{ github.event.pull_request.number || github.ref_name }}" "$GITHUB_RUN_ID"; fi
- name: Shadow verdict
if: always()
env:
SHADOW_SHA: ${{ inputs.sha }}
JOB_STATUS: ${{ job.status }}
run: |
result=failure; [ "$JOB_STATUS" = success ] && result=success
echo "SHADOW-VERDICT sha=${SHADOW_SHA} runner=${RUNNER_NAME} result=${result}"
2 changes: 1 addition & 1 deletion .github/workflows/mutants-nightly.yml
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ name: Mutants Nightly

on:
schedule:
- cron: '0 3 * * 0' # Sunday 03:00 UTC: the quietest PR hours (03-05Z, 10 of 300 PR CI runs 2026-09-29..10-06) on the quietest day
- cron: '0 1 * * 0' # Sunday 01:00 UTC (mutants 180 + stop 5 ends 04:05Z): the quietest PR hours (03-05Z, 10 of 300 PR CI runs 2026-09-29..10-06) on the quietest day
workflow_dispatch: {}

permissions:
Expand Down
Loading