CI #8732
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
| # Sovereign CI — calls reusable workflow from paiml/.github | |
| # Change once in paiml/.github → applies to all repos | |
| # | |
| # Jobs provided by sovereign-ci.yml: | |
| # test: cargo test --lib (self-hosted clean-room) | |
| # lint: cargo clippy --all-targets -- -D warnings + cargo fmt --check | |
| # coverage: cargo llvm-cov + codecov upload | |
| # security: cargo audit (ubuntu-latest, continue-on-error) | |
| # provenance: SLSA attest-build-provenance | |
| # gate: aggregates test+lint results | |
| name: CI | |
| on: | |
| push: | |
| branches: [main, master] | |
| pull_request: | |
| branches: [main, master] | |
| # Merge queue: GitHub dispatches a `merge_group` event for each batch it forms. | |
| # Both required checks (`ci / gate` via the reusable sovereign-ci call, and the | |
| # top-level `workspace-test` job) live in THIS workflow, so triggering here makes | |
| # both run against the queued batch. Without this trigger, enabling the queue jams | |
| # every PR (no checks ever run on the merge_group ref → nothing merges). | |
| merge_group: | |
| workflow_dispatch: | |
| concurrency: | |
| group: ci-${{ github.event.pull_request.number || github.ref }} | |
| cancel-in-progress: true | |
| jobs: | |
| ci: | |
| uses: paiml/.github/.github/workflows/sovereign-ci.yml@main | |
| with: | |
| repo: ${{ github.event.repository.name }} | |
| # Phase 3 pilot (heavy workload) — build-performance.md §7 Phase 3. | |
| # APR-MONO monorepo: 879+ compile units, largest dep graph in the fleet. | |
| # Highest expected sccache hit-rate lift; without it each PR cold-compiles | |
| # in its per-PR-per-run target dir | |
| # (`/mnt/nvme-raid0/targets/aprender-ci/<PR>/run-<RUN_ID>`) | |
| # for ~34min, leaving only ~4min for tests inside the 40min timeout — | |
| # the entire merge queue saturates. | |
| # | |
| # 2026-05-15: target-dir path bumped from `aprender-ci/<PR>` to | |
| # `aprender-ci/<PR>/run-<RUN_ID>` to break the cancel-corrupt-state | |
| # race introduced by the prior per-PR fix (paiml/.github#31, | |
| # 2026-04-23). `concurrency.cancel-in-progress: true` + persistent | |
| # per-PR mount = the SIGTERM→SIGKILL window of a dying old cargo | |
| # corrupted `/workspace/target/debug/deps/` for the new run that | |
| # mounted the same host path. sccache stays on its own mount, so | |
| # cross-run cache effectiveness is preserved; only cargo-incremental | |
| # state (small fraction of total compile) is lost per new run. | |
| # | |
| # 2026-04-19: temporarily disabled — sovereign-ci:stable container image | |
| # was missing the `rustc-sccache` wrapper script. Fixed upstream in | |
| # paiml/infra commit f4fccf9 (PR #66, "use exec script not symlink"). | |
| # 2026-05-12: re-enabled — image verified to ship `/usr/local/bin/rustc-sccache` | |
| # (sccache 0.14.0), shared cache at `/home/noah/data/sccache` (warm, ~11GB). | |
| enable_sccache: true | |
| use_nextest: true | |
| # NOTE: coverage_min (the opt-in coverage ratchet from the build-system audit) | |
| # is intentionally NOT set on aprender. The pilot run exposed that aprender's | |
| # ROOT crate is a facade — the sovereign-ci coverage job runs `--lib` on the | |
| # root and exercises 0 tests ("test result: ok. 0 passed"); all real code + | |
| # tests live in workspace members, run by the separate `workspace-test` job. | |
| # So coverage_min has no lcov data to gate on. Making it meaningful here needs | |
| # test_workspace: true + GPU-member test_args exclusions first (the PMAT-159 | |
| # workspace blind-spot). Tracked as a follow-up. The coverage ratchet | |
| # MECHANISM is live fleet-wide via sovereign-ci (#37); a single-crate repo is | |
| # the natural first coverage pilot. aprender's blocking-quality pilot is the | |
| # diff-scoped mutation gate below. | |
| secrets: inherit | |
| # APR-MONO: Workspace-wide test (all 75 crates) | |
| # | |
| # 2026-05-13: Refactored from GH Actions `container:` syntax (which forces an | |
| # unconditional `docker pull` with only 3 retries / ~6s total backoff) to | |
| # explicit `docker run` steps with a 15-attempt linear-backoff pull retry. | |
| # The previous design conflated "image is required" with "registry must be | |
| # reachable at pull time" — when `localhost:5000` blipped (registry restart, | |
| # network reload), the pull failed and the whole job died after ~25s. This | |
| # refactor preserves the same execution semantics (same image, same volume | |
| # mounts, same env) but moves the pull into a step the workflow controls, | |
| # giving us up to ~13 minutes of retry headroom before declaring the | |
| # registry unreachable. Mirrored in the `mutants` job below. | |
| workspace-test: | |
| runs-on: [self-hosted, X64, Linux, clean-room] | |
| timeout-minutes: 85 # bumped to match 75min step + 10min overhead | |
| env: | |
| IMAGE: localhost:5000/sovereign-ci:stable | |
| PR_OR_REF: ${{ github.event.pull_request.number || github.ref_name }} | |
| steps: | |
| - name: Pre-checkout ownership restore (EACCES self-heal) | |
| # Five-whys: the end-of-job "Fix file ownership" step is | |
| # `if: always()`, but a hard-killed job (runner death, forced | |
| # cancel) skips even always() steps → root-owned | |
| # target/.rustc_info.json + target/package/ survive in the runner | |
| # checkout → the NEXT run's actions/checkout `git clean -ffdx` | |
| # fails with EACCES (observed 2026-07-02: 4 jobs across PRs | |
| # #2257/#2258 on runners 10/14/16, each needing a manual | |
| # `ssh intel sudo rm` sweep). Restoring ownership BEFORE checkout | |
| # makes every run self-healing instead of depending on the | |
| # previous run's clean exit. | |
| # Soundness of the cached-image gate: leftovers can only exist if | |
| # a previous docker job ran on this runner — which implies the | |
| # image is already in the local cache. So "image not cached ⟹ no | |
| # leftovers" and skipping is safe (also keeps this step | |
| # registry-outage-tolerant). | |
| run: | | |
| if docker image inspect "$IMAGE" > /dev/null 2>&1; then | |
| docker run --rm -v "${GITHUB_WORKSPACE}:/workspace" "$IMAGE" \ | |
| bash -c 'chown -R 1000:1000 /workspace 2>/dev/null || true' | |
| else | |
| echo "Image not cached — no prior docker job on this runner, nothing to restore" | |
| fi | |
| - uses: actions/checkout@v7 | |
| - name: Pull sovereign-ci image (with retry + local-cache fallback) | |
| # Self-hosted runner's local Docker registry at localhost:5000 is | |
| # occasionally restarting OR experiencing extended outages | |
| # (paiml/infra ops). Two layers of resilience: | |
| # 1. Check the local Docker daemon cache first — the image was | |
| # successfully pulled on a prior run, so it's almost certainly | |
| # still in the cache (Docker doesn't GC images unless `prune` | |
| # is run). If present, skip the pull entirely; this makes the | |
| # workflow registry-outage-tolerant. | |
| # 2. Otherwise, try to pull with 15-attempt linear-backoff retry | |
| # (~13min total) — plenty for any normal restart cycle. | |
| # The local-cache path accepts slight staleness as the price of | |
| # registry-outage tolerance. paiml/infra:machines/intel/sovereign- | |
| # ci/rebuild.sh rebuilds the stable tag nightly so any drift gets | |
| # corrected within 24h on the next successful pull. | |
| run: | | |
| if docker image inspect "$IMAGE" > /dev/null 2>&1; then | |
| echo "Image $IMAGE already cached locally — skipping pull" | |
| echo "(local cache is registry-outage-tolerant; nightly rebuild keeps it fresh)" | |
| exit 0 | |
| fi | |
| max_attempts=15 | |
| delay=4 | |
| for i in $(seq 1 $max_attempts); do | |
| if docker pull "$IMAGE"; then | |
| echo "Image pulled successfully on attempt $i" | |
| exit 0 | |
| fi | |
| if [ $i -eq $max_attempts ]; then | |
| echo "::error::Registry localhost:5000 unreachable after $max_attempts attempts (~13min) AND image not in local cache" | |
| echo "::error::Suggests paiml/infra runner-side registry restart + initial image seed needed" | |
| exit 1 | |
| fi | |
| echo "Pull attempt $i/$max_attempts failed; sleeping ${delay}s" | |
| sleep "$delay" | |
| delay=$((delay + 6)) # linear backoff: 4,10,16,22,28,34,... | |
| done | |
| - name: Pre-flight target-dir consistency check | |
| # Root cause (five-whys): | |
| # 1. Why does workspace-test sometimes fail with "no such file or | |
| # directory .rcgu.o" / extern location missing / cc-rs can't | |
| # create .o? Cargo's incremental state on the per-PR target | |
| # dir is inconsistent. | |
| # 2. Why inconsistent? A prior run was SIGKILL'd mid-compile and | |
| # left orphan .rmeta files (parts cargo had registered as built) | |
| # without the corresponding .rcgu.o codegen artifacts (which were | |
| # mid-write at the moment of the kill). | |
| # 3. Why was it SIGKILL'd? concurrency.cancel-in-progress (line 22) | |
| # cancels the previous run as soon as a new commit lands on the | |
| # branch (and "Update branch" / strict-up-to-date triggers this | |
| # every time aprender main moves forward). | |
| # 4. Why does this persist? The target dir is bind-mounted from a | |
| # per-PR persistent path /mnt/nvme-raid0/targets/aprender-ci/<PR>/, | |
| # so partial-compile state survives across runs. | |
| # 5. Root cause: cargo's incremental state is not atomic-on-kill, so | |
| # a persistent shared target dir + cancel-in-progress = damage. | |
| # Prevention (this step): BEFORE invoking cargo, check whether the | |
| # immediately-preceding workflow run on this branch was cancelled. If | |
| # yes, rm -rf the target dir contents. This is a one-time check at | |
| # job start — NOT a retry-on-failure pattern (which the operator | |
| # rejects under the "flake is not allowed" directive). | |
| run: | | |
| set -e | |
| if [ -z "${{ github.event.pull_request.number }}" ]; then | |
| echo "Not a PR run; skipping prior-cancel check" | |
| exit 0 | |
| fi | |
| # Find the immediately-preceding workflow run on this branch. | |
| # status=completed filter excludes the current in-progress run. | |
| PREV_CONCLUSION=$(gh api \ | |
| "repos/${GITHUB_REPOSITORY}/actions/runs?branch=${GITHUB_HEAD_REF}&status=completed&per_page=1" \ | |
| --jq '.workflow_runs[0].conclusion' 2>/dev/null || echo "") | |
| echo "Previous run conclusion on ${GITHUB_HEAD_REF}: ${PREV_CONCLUSION:-<none>}" | |
| if [ "$PREV_CONCLUSION" = "cancelled" ]; then | |
| echo "::warning::Previous run was cancelled; nuking target dir to prevent cargo cancel-damage" | |
| docker run --rm \ | |
| -v "/mnt/nvme-raid0/targets/aprender-ci/${PR_OR_REF}:/workspace/target" \ | |
| "$IMAGE" \ | |
| bash -c 'rm -rf /workspace/target/* /workspace/target/.[!.]* 2>/dev/null || true; ls -la /workspace/target/ || true' | |
| else | |
| echo "No cancel damage to clean (prior conclusion: ${PREV_CONCLUSION:-fresh-branch})" | |
| fi | |
| env: | |
| GH_TOKEN: ${{ github.token }} | |
| - name: Pre-build chown — fix per-RUN root ownership | |
| # Root cause (five-whys): | |
| # 1. Why do fresh runs sometimes fail with "failed to create | |
| # /workspace/target/debug" / "No such file or directory"? | |
| # Cargo (running as user 1000 inside the container) can't | |
| # write to /workspace/target/debug. | |
| # 2. Why can't it write? The bind-mount source dir on the host | |
| # (/mnt/nvme-raid0/targets/aprender-ci/<PR>/run-<RUN_ID>) is | |
| # owned by root:root. | |
| # 3. Why is it root-owned? Docker's bind-mount creates missing | |
| # host directories with the daemon's uid (root). Per-RUN | |
| # paths are always fresh, so this fires every run. | |
| # 4. Why didn't this happen before? Pre-#1693 the per-PR (not | |
| # per-RUN) path persisted across runs, and the downstream | |
| # "post-job cleanup" docker chown step fixed ownership for | |
| # the NEXT run's git-clean. Per-RUN paths invalidate that — | |
| # each run gets a brand-new root-owned dir. | |
| # 5. Root cause: the chown step runs AFTER cargo, not BEFORE. | |
| # First-runs always fail; reruns appear to work only when | |
| # the previous run's belated chown fixed the now-stale dir. | |
| # Fix (this step): docker run as root, chown the per-RUN target | |
| # dir + cargo registry to noah:1000 BEFORE the cargo step. | |
| # Idempotent — `|| true` tolerates dirs that are already | |
| # noah-owned (e.g. a rerun of the same run-id). | |
| run: | | |
| docker run --rm \ | |
| -v "/mnt/nvme-raid0/targets/aprender-ci/${PR_OR_REF}/run-${GITHUB_RUN_ID}:/workspace/target" \ | |
| -v "/mnt/nvme-raid0/cargo-ci/registry/${PR_OR_REF}:/usr/local/cargo/registry" \ | |
| "$IMAGE" \ | |
| bash -c 'chown -R 1000:1000 /workspace/target /usr/local/cargo/registry 2>/dev/null || true' | |
| - name: Workspace lib tests (25,300+) | |
| # Excluded: aprender-gpu (cuBLAS), aprender-cuda-edge (CUDA), aprender-compute (SIMD SIGSEGV at exit) | |
| # Timeout: 75min (was 55, was 40). | |
| # 2026-05-15: bumped to 75min after the P0 per-run target-dir fix | |
| # (#1693) eliminated cargo-incremental cross-run warmth. Cold | |
| # compiles now happen on every run; sccache covers codegen | |
| # (~80% hit rate on warm cache) but cargo's metadata + linking + | |
| # test binaries still cost ~40-50min cold. Under runner-pool | |
| # saturation (7+ concurrent CI runs) we observed 55min hits | |
| # exactly at the timeout — runs 25919246467 / 25919258460 / | |
| # sibling PRs failed simultaneously at 55:00.0. | |
| # | |
| # 2026-06-24 (perf/ci-nextest, EXPERIMENT): switched from | |
| # `cargo test --workspace --lib` to `cargo nextest run`. Profiling the | |
| # ~40min job showed ~80% is SERIAL test EXECUTION (cargo's libtest runs | |
| # one test binary at a time; ~27min of the wall clock is just running | |
| # tests), not codegen (sccache already covers that). nextest runs every | |
| # test in its own process across a thread pool, so the ~25,300 tests | |
| # execute in PARALLEL across crates — the big lever. The exact | |
| # `--exclude` list is preserved verbatim (aprender-compute stays | |
| # excluded; its SIGSEGV-at-exit is handled by the dedicated "Compute | |
| # tests" step below). `--profile ci` uses .config/nextest.toml | |
| # ([profile.ci]: retries=0, fail-fast). line-tables-only debuginfo now | |
| # comes from root Cargo.toml [profile.test]; the CARGO_PROFILE_*_DEBUG | |
| # env vars are kept as belt-and-suspenders. | |
| timeout-minutes: 75 | |
| run: | | |
| docker run --rm \ | |
| -e CI -e GITHUB_ACTIONS -e GITHUB_REF -e GITHUB_SHA -e GITHUB_REPOSITORY -e GITHUB_RUN_ID -e GITHUB_EVENT_NAME -e GITHUB_WORKFLOW \ | |
| -v "${GITHUB_WORKSPACE}:/workspace" \ | |
| -v "/mnt/nvme-raid0/cargo-ci/registry/${PR_OR_REF}:/usr/local/cargo/registry" \ | |
| -v "/mnt/nvme-raid0/targets/aprender-ci/${PR_OR_REF}/run-${GITHUB_RUN_ID}:/workspace/target" \ | |
| -v "/home/noah/data/sccache:/sccache" \ | |
| -w /workspace \ | |
| -e CARGO_TARGET_DIR=/workspace/target \ | |
| -e RUSTC_WRAPPER=rustc-sccache \ | |
| -e SCCACHE_DIR=/sccache \ | |
| -e CARGO_INCREMENTAL=0 \ | |
| -e CARGO_BUILD_JOBS=8 \ | |
| -e CARGO_PROFILE_TEST_DEBUG=line-tables-only \ | |
| -e CARGO_PROFILE_DEV_DEBUG=line-tables-only \ | |
| "$IMAGE" \ | |
| cargo nextest run --profile ci --workspace --lib --exclude aprender-gpu --exclude aprender-cuda-edge --exclude aprender-compute | |
| - name: Compute tests (tolerate SIGSEGV at exit — all tests pass but harness crashes on cleanup) | |
| run: | | |
| docker run --rm \ | |
| -e CI -e GITHUB_ACTIONS -e GITHUB_REF -e GITHUB_SHA -e GITHUB_REPOSITORY -e GITHUB_RUN_ID -e GITHUB_EVENT_NAME -e GITHUB_WORKFLOW \ | |
| -v "${GITHUB_WORKSPACE}:/workspace" \ | |
| -v "/mnt/nvme-raid0/cargo-ci/registry/${PR_OR_REF}:/usr/local/cargo/registry" \ | |
| -v "/mnt/nvme-raid0/targets/aprender-ci/${PR_OR_REF}/run-${GITHUB_RUN_ID}:/workspace/target" \ | |
| -v "/home/noah/data/sccache:/sccache" \ | |
| -w /workspace \ | |
| -e CARGO_TARGET_DIR=/workspace/target \ | |
| -e RUSTC_WRAPPER=rustc-sccache \ | |
| -e SCCACHE_DIR=/sccache \ | |
| -e CARGO_INCREMENTAL=0 \ | |
| -e CARGO_BUILD_JOBS=8 \ | |
| "$IMAGE" \ | |
| bash -c 'cargo test -p aprender-compute --lib 2>&1 | tee /tmp/compute-test.log; grep -q "test result: ok\." /tmp/compute-test.log && ! grep -q "test result: FAILED" /tmp/compute-test.log' | |
| - name: Integration tests | |
| # #2465: the four FALSIFY-AUTH targets were appended here because they | |
| # were DARK — `falsify_auth_002` appeared nowhere in .github/, scripts/ | |
| # or Makefile, and neither did the contract loader | |
| # `apr_serve_api_key_auth_contract` that is supposed to promote | |
| # apr-serve-api-key-auth-v1 from DRAFT to ACTIVE. All 17 tests pass; they | |
| # simply never ran. This is also the line that hides such targets: adding | |
| # a `tests/*.rs` file does nothing until its name appears HERE, and only | |
| # one PR at a time may edit this single physical line without hitting a | |
| # merge-queue conflict. | |
| # | |
| # perf/ci-nextest (rank 3 — integration collapse): INTENTIONALLY SKIPPED. | |
| # The investigation ranked collapsing these 8 `cargo test -p X --test Y` | |
| # invocations into ONE `cargo nextest run -E '...'` as rank 3 (low lever: | |
| # this step is NOT the ~27min bottleneck — the lib step is). It also | |
| # carries a real risk: this chain includes | |
| # `cargo test -p aprender-compute --lib beat_nf4_bitsandbytes_equivalence`, | |
| # and aprender-compute SIGSEGVs at harness EXIT. Under cargo's libtest a | |
| # single-name filter exits cleanly here, but nextest's per-test process | |
| # model would observe the segfaulting process exit code and could fail | |
| # the run. Per the experiment brief ("if fiddly/risky, SKIP — don't block | |
| # the experiment"), this step is left AS-IS so the nextest signal stays | |
| # attributable to the lib step alone. Revisit after the lib-step | |
| # measurement lands. | |
| run: | | |
| docker run --rm \ | |
| -e CI -e GITHUB_ACTIONS -e GITHUB_REF -e GITHUB_SHA -e GITHUB_REPOSITORY -e GITHUB_RUN_ID -e GITHUB_EVENT_NAME -e GITHUB_WORKFLOW \ | |
| -v "${GITHUB_WORKSPACE}:/workspace" \ | |
| -v "/mnt/nvme-raid0/cargo-ci/registry/${PR_OR_REF}:/usr/local/cargo/registry" \ | |
| -v "/mnt/nvme-raid0/targets/aprender-ci/${PR_OR_REF}/run-${GITHUB_RUN_ID}:/workspace/target" \ | |
| -v "/home/noah/data/sccache:/sccache" \ | |
| -w /workspace \ | |
| -e CARGO_TARGET_DIR=/workspace/target \ | |
| -e RUSTC_WRAPPER=rustc-sccache \ | |
| -e SCCACHE_DIR=/sccache \ | |
| -e CARGO_INCREMENTAL=0 \ | |
| -e CARGO_BUILD_JOBS=8 \ | |
| "$IMAGE" \ | |
| bash -c 'cargo test -p aprender-core --test monorepo_invariants && cargo test -p aprender-core --test readme_contract && cargo test -p apr-cli --test cli_commands && cargo test -p aprender-core --test beat_sklearn_iris && cargo test -p aprender-core --test beat_sklearn_nmi && cargo test -p aprender-core --test beat_sklearn_metrics_parity && cargo test -p aprender-core --test beat_sklearn_gaussiannb_accuracy && cargo test -p aprender-core --test beat_sklearn_svc_accuracy && cargo test -p aprender-core --test beat_sklearn_pipeline_encoder && cargo test -p aprender-serve --test beat_fail_closed_garbage && cargo test -p aprender-compute --lib beat_nf4_bitsandbytes_equivalence && cargo test -p aprender-core --test beat_pytorch_autograd_grad && cargo test -p aprender-train-lora --lib beat_lora_merge_forward_equivalence && cargo test -p apr-cli --release --test beat_pytorch_deploy_footprint && cargo test -p aprender-serve --test beat_fail_closed_structural && cargo test -p aprender-serve --test ollama_http_compat && cargo test -p apr-cli --test ollama_ndjson_streaming && cargo test -p apr-cli --test falsification_chat_http_cli && cargo test -p aprender-contracts --test apr_serve_api_key_auth_contract && cargo test -p apr-cli --test falsify_auth_001 --test falsify_auth_002 --test falsify_auth_003 --no-fail-fast && cargo build --examples --workspace --keep-going' | |
| - name: Build.rs crate-root escape check (v0.31.1 yank guard) | |
| # Static Poka-Yoke: flags build.rs files that panic on files outside | |
| # CARGO_MANIFEST_DIR, which break `cargo install` from crates.io. | |
| # See scripts/check_build_rs_paths.sh for the full rationale. | |
| run: | | |
| docker run --rm \ | |
| -e CI -e GITHUB_ACTIONS -e GITHUB_REF -e GITHUB_SHA -e GITHUB_REPOSITORY -e GITHUB_RUN_ID -e GITHUB_EVENT_NAME -e GITHUB_WORKFLOW \ | |
| -v "${GITHUB_WORKSPACE}:/workspace" \ | |
| -w /workspace \ | |
| "$IMAGE" \ | |
| bash scripts/check_build_rs_paths.sh | |
| - name: apr-format leaf sovereignty guard (#2231) | |
| # Poka-Yoke: prove the extracted `apr-format` leaf pulls no ML/GPU/ | |
| # tokenizer/framework crate (so consumers `cargo add apr-format` without | |
| # aprender-core + trueno/wgpu). Discriminating: PASSES on apr-format + | |
| # aprender-quant, FAILS on aprender-core. Also runs a publish dry-run to | |
| # catch dev-dep cycles. See scripts/check_format_sovereignty.sh. | |
| run: | | |
| docker run --rm \ | |
| -e CI -e GITHUB_ACTIONS -e GITHUB_REF -e GITHUB_SHA -e GITHUB_REPOSITORY -e GITHUB_RUN_ID -e GITHUB_EVENT_NAME -e GITHUB_WORKFLOW \ | |
| -v "${GITHUB_WORKSPACE}:/workspace" \ | |
| -w /workspace \ | |
| "$IMAGE" \ | |
| bash scripts/check_format_sovereignty.sh | |
| - name: Fix file ownership (container runs as root, runner as noah:1000) | |
| if: always() | |
| run: | | |
| # Five-whys: Docker container creates files as root on bind-mounted | |
| # workspace. Runner (noah:1000) can't git-clean them on next run | |
| # → checkout fails → CI breaks. This runs inside the container | |
| # (as root) restoring host ownership for subsequent bare-metal jobs. | |
| docker run --rm \ | |
| -e CI -e GITHUB_ACTIONS -e GITHUB_REF -e GITHUB_SHA -e GITHUB_REPOSITORY -e GITHUB_RUN_ID -e GITHUB_EVENT_NAME -e GITHUB_WORKFLOW \ | |
| -v "${GITHUB_WORKSPACE}:/workspace" \ | |
| -v "/mnt/nvme-raid0/cargo-ci/registry/${PR_OR_REF}:/usr/local/cargo/registry" \ | |
| -v "/mnt/nvme-raid0/targets/aprender-ci/${PR_OR_REF}/run-${GITHUB_RUN_ID}:/workspace/target" \ | |
| "$IMAGE" \ | |
| bash -c 'chown -R 1000:1000 /workspace || true; chown -R 1000:1000 /usr/local/cargo/registry || true; chown -R 1000:1000 /workspace/target || true' | |
| # Poka-yoke (aprender#2269): fail fast if ANY self-hosted job omits a | |
| # discriminating runner label, so a bare [self-hosted, X64, Linux] selector | |
| # can never again silently land a job on a GPU/dev runner without the | |
| # sovereign-ci registry. Pure text check; runs on the clean-room pool. | |
| guard-runner-labels: | |
| runs-on: [self-hosted, X64, Linux, clean-room] | |
| env: | |
| IMAGE: localhost:5000/sovereign-ci:stable | |
| steps: | |
| - name: Pre-checkout ownership restore (EACCES self-heal) | |
| # Same guard as workspace-test (:92) and mutants (:435). This job was | |
| # the ONLY clean-room job missing it, and that asymmetry took main red | |
| # on 2026-07-06 (run 28776891625): a hard-killed docker job left | |
| # root-owned target/.rustc_info.json + target/package/ in the runner | |
| # checkout, so this job's actions/checkout `git clean -ffdx` failed | |
| # with EACCES before a single step ran. Because `gate` hard-requires | |
| # guard-runner-labels (:379), a checkout-level EACCES here reads as a | |
| # required-check failure and blocks every merge — the andon stays red | |
| # until someone manually `ssh intel sudo rm`s the leftovers. | |
| # Cached-image gate is sound: leftovers can only exist if a previous | |
| # docker job ran on this runner, which implies the image is already in | |
| # the local cache. So "image not cached ⟹ no leftovers" and skipping is | |
| # safe (and keeps this step tolerant of a registry outage). | |
| run: | | |
| if docker image inspect "$IMAGE" > /dev/null 2>&1; then | |
| docker run --rm -v "${GITHUB_WORKSPACE}:/workspace" "$IMAGE" \ | |
| bash -c 'chown -R 1000:1000 /workspace 2>/dev/null || true' | |
| else | |
| echo "Image not cached — no prior docker job on this runner, nothing to restore" | |
| fi | |
| - uses: actions/checkout@v7 | |
| with: | |
| fetch-depth: 1 | |
| - name: Every self-hosted job must pin a discriminating label | |
| run: bash scripts/check_runner_labels.sh | |
| # Poka-yoke: a beat that no workflow executes reads as enforcement, is | |
| # counted as enforcement, and proves nothing. The Pillar-4 marquee decode | |
| # beat sat in ZERO workflows while being quoted as an enforced win (#2319). | |
| # Pure text check, no build, so it belongs in this job rather than paying | |
| # for its own runner. | |
| - name: Every beat must be executed by some workflow | |
| run: bash scripts/check_beats_gated.sh | |
| # Poka-yoke: PMAT-CI-PASSGREP-001 killed ONE `grep "0 failed"` that also | |
| # matched "10 failed". Two more instances of the same class survived that | |
| # fix, one of them a live contract falsifier. Probe every zero-count | |
| # pass-grep against an all-failing line instead of trusting review. | |
| - name: Every zero-count pass-grep must reject a failing line | |
| run: bash scripts/check_pass_grep_anchored.sh | |
| # Poka-yoke: a bare `apr` runs whatever PATH resolves. qwen-story-daily | |
| # installed 0.61.0 to ~/.cargo/bin and then executed a 24-day-old 0.60.0 | |
| # from ~/.local/bin, so every beat validated stale code while reporting | |
| # green. Text-only check, no build. | |
| - name: Every execution-surface `apr` reference must be pinned | |
| run: bash scripts/check_apr_bin_pinned.sh | |
| # The guard's own must-match/must-not-match table plus one mutation per | |
| # SCANNED SURFACE, in that surface's own syntax. #2358: extending the | |
| # scope to scripts/** and .claude/skills/** did NOT carry the old proof | |
| # over - the Makefile lesson repeated itself twice (a skill bash fence and | |
| # a `!`...`` inline command were both invisible to a pattern that was | |
| # correct everywhere else). Run the table; do not re-read the regex. | |
| - name: "`apr`-pinning guard must still turn RED (case table + surfaces)" | |
| run: bash scripts/check_apr_bin_pinned.sh --self-test | |
| # deny.toml is 7.2 KB of licence / banned-crate / source-allowlist policy | |
| # that ran in NO workflow -- only `make deny`, which is not a prerequisite | |
| # of any tier, so nothing but a human typing it ever invoked it. Meanwhile | |
| # cargo-deny 0.20.2 ships INSIDE the CI image already. | |
| # | |
| # Because nothing ran it, the policy drifted out of sync with the repo's | |
| # own deliberate architecture and now fails on main: | |
| # advisories ok, bans FAILED, licenses FAILED, sources ok | |
| # Both failures were config drift, not defects (see the deny.toml comments). | |
| # Fixed there; wired here so it cannot drift unnoticed again. | |
| # | |
| # `ci / security` from the reusable workflow runs `cargo audit`, which does | |
| # NOT read deny.toml -- so this covers licences, bans and sources, which | |
| # nothing else does. | |
| - name: cargo-deny (licences, bans, sources, advisories) | |
| run: | | |
| docker run --rm \ | |
| -v "${GITHUB_WORKSPACE}:/workspace" \ | |
| -w /workspace \ | |
| "$IMAGE" \ | |
| cargo deny check | |
| # Poka-yoke: `set` in a SOURCED file mutates the caller's shell. apr_bin.sh | |
| # opened with `set -euo pipefail`; qwen-story.sh sources it and had chosen | |
| # `set -uo pipefail` deliberately (it must run every beat and tally the | |
| # failures). The leak turned errexit on underneath it and the nightly | |
| # story died after six lines inside an ADVISORY pmat hunt. Both files read | |
| # correctly on their own - only the combination is wrong, which is why | |
| # this is mechanical rather than a review note. Text-only, no build. | |
| - name: Sourced libraries must not mutate the caller's shell options | |
| run: bash scripts/check_sourced_libs_option_neutral.sh --self-test | |
| # Poka-yoke: a Cargo `exclude` entry is a gitignore pattern, so a bare | |
| # `"tests/"` is NOT anchored to the package root - it matches at every | |
| # depth. CB-510 was the same bug with `"models/"` hiding `src/models/`. | |
| # In v0.63.0 it dropped 443 files from the published aprender-serve and | |
| # left 11 (serve) + 18 (train) `mod tests;` declarations pointing at | |
| # directories that are not in the tarball, so neither published crate can | |
| # compile its own test suite. Text-only check, no build. | |
| - name: No exclude pattern may swallow a src/ directory | |
| run: bash scripts/check_exclude_anchored.sh | |
| # Poka-yoke: capturing `cmd 2>&1` into one variable and then parsing it as | |
| # JSON puts the command's diagnostics in front of its data. On 2026-08-11 | |
| # the nightly story reported `no format_parity gate found in --json output | |
| # (got: '')` for a gate that had run and PASSED, because `apr qa --json` | |
| # writes JSON to stdout while the CUDA path emits `[trueno#243]` lines to | |
| # stderr. A false FAIL reddens main for a non-reason. The converse matters | |
| # too: a Rust panic arrives on stderr, so the panic checks must keep | |
| # reading the merged view. Behavioural, text-only, no build. | |
| - name: Story JSON parsing must read stdout, not the merged stream | |
| run: bash scripts/check_story_json_streams.sh | |
| # Poka-yoke: a path filter is a claim that nothing outside those paths can | |
| # break the gate. book.yml filtered on `book/**`, so the CLI/book parity gate | |
| # never ran when the CLI gained a command — `apr beat-run` shipped in #1995 | |
| # with no chapter and the gate stayed green for three months, hiding a lib | |
| # parity failure, a `pv` step that exited 127, and two chapter examples | |
| # training to NaN behind it. Text-only check, no build. | |
| - name: A path-filtered workflow must not be able to go dark | |
| run: | | |
| bash scripts/check_workflow_path_filters.sh --self-test | |
| bash scripts/check_workflow_path_filters.sh | |
| # Poka-yoke: mac-server runs 16 runners under ONE $HOME, so | |
| # $HOME/.cargo/bin is shared mutable state. `cargo install` replaces a | |
| # binary there while another job is mid-run and about to exec it — on | |
| # 2026-07-31 Coverage Nightly died on | |
| # `could not execute process .../cargo-llvm-cov ... No such file or | |
| # directory (os error 2)` and posted an EMPTY coverage figure, i.e. a | |
| # missing measurement (#2353). Text-only check, no build. | |
| - name: Self-hosted jobs must install cargo tools into a private root | |
| run: | | |
| bash scripts/check_cargo_install_private_root.sh --self-test | |
| bash scripts/check_cargo_install_private_root.sh | |
| # Poka-yoke: the nightly's pmat bug-hunt printed eight manifest headers and | |
| # ZERO rows every night since the cron was added (#2356), so the workflow's | |
| # "manifest grew by >5" alert branch never had a non-zero input. Three | |
| # causes, each silent: the jq filters named `.function`/`.churn.commit_count` | |
| # /`.faults` where pmat emits `function_name`/`commit_count`/ | |
| # `fault_annotations`; the churn and fault hunts passed the beat label as a | |
| # free-text query, which is a relevance filter applied BEFORE `--path` and | |
| # collapses a module-scoped hunt to nothing; and three hunted paths had been | |
| # deleted or moved. All three present as "the manifest is empty tonight", | |
| # which used to be indistinguishable from "the code is clean tonight" - | |
| # pmat_hunt returned 0 unconditionally. It now fails the beat. Behavioural | |
| # against a stubbed pmat, text-only, no build. | |
| - name: The pmat bug-hunt manifest must be able to produce rows | |
| run: bash scripts/check_story_pmat_hunt.sh | |
| # Poka-yoke: the 0.63.0 dogfood audit found 201 defects in code that was | |
| # shipped, tested and covered. The 5 Whys (docs/audits/dogfood-0.63.0-hansei.md) | |
| # landed on: nothing mechanical requires an assertion to EXCLUDE an outcome. | |
| # `assert!(status == OK || status == BAD_REQUEST || status == NOT_IMPLEMENTED)` | |
| # passes whatever the endpoint does, earns identical line coverage to an | |
| # assert_eq!, and is exempt from diff-scoped mutation testing on | |
| # pre-existing surface. 320 such sites existed; the first one tightened | |
| # showed a test named `test_apr_explain_endpoint` never reaching the | |
| # explain handler at all. Baseline-ratcheted so the debt only falls. | |
| # Text-only check, no build. | |
| - name: Assertions must exclude an outcome, not merely reach one | |
| run: bash scripts/check_assertions_exclude.sh | |
| - name: Assertion-exclusion guard case table | |
| run: bash scripts/check_assertions_exclude.sh --self-test | |
| # FALSIFY-README-00*: README metric claims must match measurement. The | |
| # guard existed and ran nowhere -- its only caller was scripts/dogfood-book.sh, | |
| # which is itself in no workflow and no Makefile target, so it was | |
| # unreachable. It was also pattern-narrow: it matched `**M** provable | |
| # contracts` and took `head -1`, while the README carried THREE different | |
| # contract counts (1771 in the table, 1158 at line 225, 1767 at line 256). | |
| # It now checks every contract-count claim, so the README cannot contradict | |
| # itself either. Text-only, no build. | |
| - name: README claims must match measurement | |
| run: bash scripts/check_readme_claims.sh | |
| # Three guards that existed and could not block a merge. The publish-safety | |
| # one is the gate credited with keeping a 28 MB test.apr out of a published | |
| # package, and it was `make`-only -- invoked by whoever remembered to type | |
| # it. Its size check also looked at 2 of 72 publishable crates and never | |
| # measured a size, so the 5.4 MB of .pmat-baseline.json it now catches had | |
| # been shipping to crates.io unseen. | |
| - name: Publish safety (binary/oversize files in published packages) | |
| run: bash scripts/check_publish_safety.sh | |
| - name: Every include!() file is tracked by git (CB-510) | |
| run: bash scripts/check_include_files.sh | |
| - name: Book rust examples compile | |
| run: bash scripts/check_book_examples_compile.sh | |
| # Poka-yoke: APR-MONO made every sibling a path alias under crates/, but | |
| # `trueno = "0.16"` still BUILDS - cargo resolves the crates.io copy | |
| # alongside the in-tree one, so the tree compiles two mutually | |
| # type-incompatible `trueno`s and the published-crate dependency cycle the | |
| # consolidation removed comes back. Nothing goes red; you only see it in | |
| # `cargo tree --duplicates`. At 88791ff55 the lockfile held four registry | |
| # copies of trueno beside the path one. CLAUDE.md has forbidden this in | |
| # prose since the consolidation - prose is not a gate. | |
| # The name set is package names UNION lib names, because they diverge here | |
| # (package aprender-compute has lib trueno): a guard built from package | |
| # names alone would sail straight past the one declaration it exists to | |
| # stop. Vacuity-guarded three ways, including a positive control that must | |
| # be flagged before any clean verdict is printed. Text-only, no build. | |
| # Self-test first: if the guard is blind, that matters more than its verdict. | |
| - name: Sibling-crate pathing guard case table | |
| run: bash scripts/check_workspace_siblings_pathed.sh --self-test | |
| - name: In-tree siblings must be pathed, never pulled from crates.io | |
| run: bash scripts/check_workspace_siblings_pathed.sh | |
| # Top-level gate: satisfies org ruleset "Green Main" which requires check named "gate". | |
| # The reusable workflow produces "ci / gate" but rulesets need exact match on "gate". | |
| gate: | |
| runs-on: [self-hosted, X64, Linux, clean-room] | |
| needs: [ci, workspace-test, mutants, guard-runner-labels] | |
| if: always() | |
| steps: | |
| - name: Check required jobs | |
| run: | | |
| if [ "${{ needs.guard-runner-labels.result }}" != "success" ]; then | |
| echo "guard-runner-labels failed: ${{ needs.guard-runner-labels.result }}" | |
| exit 1 | |
| fi | |
| if [ "${{ needs.ci.result }}" != "success" ]; then | |
| echo "ci failed: ${{ needs.ci.result }}" | |
| exit 1 | |
| fi | |
| if [ "${{ needs.workspace-test.result }}" != "success" ]; then | |
| echo "workspace-test failed: ${{ needs.workspace-test.result }}" | |
| exit 1 | |
| fi | |
| # Diff-scoped mutation gate (PMAT gap #1): blocking on PRs. | |
| # `skipped` is the expected result on push-to-main (the job has | |
| # `if: github.event_name == 'pull_request'`); treat it as pass so | |
| # main-branch pushes are not blocked by a job that intentionally | |
| # did not run. Only an explicit `failure` blocks. | |
| MUT="${{ needs.mutants.result }}" | |
| if [ "$MUT" = "failure" ]; then | |
| echo "mutants (diff-scoped mutation) failed: $MUT" | |
| exit 1 | |
| fi | |
| echo "mutants result: $MUT (success/skipped both pass)" | |
| echo "All required jobs passed" | |
| # Mutation testing — DIFF-SCOPED + BLOCKING on PRs (PMAT build-system audit gap #1). | |
| # | |
| # BEFORE: full-tree `cargo mutants -- --lib`, push-to-main only, and | |
| # `continue-on-error: true` at BOTH the job and step level → a surviving | |
| # mutant never blocked anything. New under-tested code merged silently, | |
| # contradicting the 80%-mutation / ZERO-tolerance rule. | |
| # | |
| # AFTER: scope mutation to the PR DIFF (`cargo mutants --in-diff`), run it on | |
| # pull_request events, and make it BLOCKING (no continue-on-error; wired into | |
| # the `gate` job). Diff-scoping is the key lever: full-tree mutation on a | |
| # 75-crate monorepo is hours-long and would choke the merge queue. Gating only | |
| # the lines a PR actually touches keeps it fast (minutes, proportional to diff | |
| # size) while still preventing NEW untested code from landing. A PR whose diff | |
| # contains no mutable code is a clean no-op pass (cargo-mutants reports 0 | |
| # mutants → exit 0). | |
| # | |
| # On a push to main (post-merge), the job is a no-op pass: there is no PR diff | |
| # to scope against, so we skip rather than fall back to the old hours-long | |
| # full-tree run. | |
| mutants: | |
| runs-on: [self-hosted, X64, Linux, clean-room] | |
| timeout-minutes: 60 | |
| needs: [ci, workspace-test] | |
| if: github.event_name == 'pull_request' | |
| env: | |
| IMAGE: localhost:5000/sovereign-ci:stable | |
| # Max surviving (missed) mutants tolerated on the PR diff. 0 = every | |
| # mutant introduced/touched by this PR must be caught by a test. Tune up | |
| # via repo variable MUTANTS_MAX_MISSED if a diff legitimately can't reach 0. | |
| MUTANTS_MAX_MISSED: ${{ vars.MUTANTS_MAX_MISSED || '0' }} | |
| steps: | |
| - name: Pre-checkout ownership restore (EACCES self-heal) | |
| # Same guard as workspace-test: a hard-killed docker job leaves | |
| # root-owned files that EACCES this job's `git clean` at checkout | |
| # (observed 2026-07-02 — the mutants job failed twice at checkout | |
| # with zero mutants actually run, reading as a gate failure). | |
| # Cached-image gate is sound: leftovers ⟹ a docker job ran here | |
| # ⟹ image is cached. | |
| run: | | |
| if docker image inspect "$IMAGE" > /dev/null 2>&1; then | |
| docker run --rm -v "${GITHUB_WORKSPACE}:/workspace" "$IMAGE" \ | |
| bash -c 'chown -R 1000:1000 /workspace 2>/dev/null || true' | |
| else | |
| echo "Image not cached — no prior docker job on this runner, nothing to restore" | |
| fi | |
| - uses: actions/checkout@v7 | |
| with: | |
| # Need history + base branch to compute the PR diff for --in-diff. | |
| fetch-depth: 0 | |
| - name: Compute PR diff for mutation scoping | |
| # cargo-mutants --in-diff takes a unified diff and mutates ONLY the | |
| # lines it adds/changes. We diff the PR head against the merge-base with | |
| # the target branch so the scope is exactly "what this PR introduces". | |
| run: | | |
| set -euo pipefail | |
| BASE_REF="${{ github.event.pull_request.base.ref }}" | |
| # NOT --depth=1: a shallow base commit has no shared ancestor with the | |
| # fetch-depth:0 PR head, so `git merge-base` finds none and (under | |
| # `set -e`) the step dies — failing the mutants gate on every PR. | |
| # Fetch the base branch's history so the merge-base is reachable. | |
| git fetch --no-tags origin "$BASE_REF" | |
| MERGE_BASE=$(git merge-base HEAD "origin/$BASE_REF" 2>/dev/null || true) | |
| if [ -z "$MERGE_BASE" ]; then | |
| echo "No merge-base with origin/$BASE_REF — scoping diff to base tip" | |
| MERGE_BASE="origin/$BASE_REF" | |
| fi | |
| echo "Base ref: $BASE_REF merge-base: $MERGE_BASE" | |
| git diff "$MERGE_BASE"...HEAD > pr.diff | |
| echo "Diff size: $(wc -l < pr.diff) lines" | |
| if [ ! -s pr.diff ]; then | |
| echo "Empty diff — no code to mutate" | |
| fi | |
| - name: Pull sovereign-ci image (with retry + local-cache fallback) | |
| # Same two-layer resilience as workspace-test — see that job for full context. | |
| run: | | |
| if docker image inspect "$IMAGE" > /dev/null 2>&1; then | |
| echo "Image $IMAGE already cached locally — skipping pull" | |
| exit 0 | |
| fi | |
| max_attempts=15 | |
| delay=4 | |
| for i in $(seq 1 $max_attempts); do | |
| if docker pull "$IMAGE"; then | |
| echo "Image pulled successfully on attempt $i" | |
| exit 0 | |
| fi | |
| if [ $i -eq $max_attempts ]; then | |
| echo "::error::Registry localhost:5000 unreachable after $max_attempts attempts AND image not in local cache" | |
| exit 1 | |
| fi | |
| echo "Pull attempt $i/$max_attempts failed; sleeping ${delay}s" | |
| sleep "$delay" | |
| delay=$((delay + 6)) | |
| done | |
| - name: Install cargo-mutants | |
| run: | | |
| docker run --rm \ | |
| -e CI -e GITHUB_ACTIONS -e GITHUB_REF -e GITHUB_SHA -e GITHUB_REPOSITORY -e GITHUB_RUN_ID -e GITHUB_EVENT_NAME -e GITHUB_WORKFLOW \ | |
| -v "${GITHUB_WORKSPACE}:/workspace" \ | |
| -w /workspace \ | |
| "$IMAGE" \ | |
| cargo install cargo-mutants --locked | |
| - name: Run diff-scoped mutation testing (BLOCKING) | |
| # No continue-on-error: a missed mutant on the PR diff fails the job, | |
| # which fails `gate`, which blocks merge. --in-diff pr.diff restricts | |
| # mutation to PR-touched lines. Empty diff → 0 mutants → clean pass. | |
| # We parse mutants.out/outcomes.json for the missed count and compare to | |
| # MUTANTS_MAX_MISSED so the threshold is explicit and tunable (rather | |
| # than relying solely on cargo-mutants' aggregate exit code). | |
| run: | | |
| set -euo pipefail | |
| if [ ! -s pr.diff ]; then | |
| echo "No PR diff content — nothing to mutate. Pass." | |
| exit 0 | |
| fi | |
| docker run --rm \ | |
| -e CI -e GITHUB_ACTIONS -e GITHUB_REF -e GITHUB_SHA -e GITHUB_REPOSITORY -e GITHUB_RUN_ID -e GITHUB_EVENT_NAME -e GITHUB_WORKFLOW \ | |
| -v "${GITHUB_WORKSPACE}:/workspace" \ | |
| -w /workspace \ | |
| -e MUTANTS_MAX_MISSED \ | |
| "$IMAGE" \ | |
| bash -c ' | |
| set -uo pipefail | |
| # --in-diff pr.diff: mutate only PR-touched lines. | |
| # cargo-mutants exits non-zero when mutants survive; we still | |
| # parse outcomes.json so the threshold (MUTANTS_MAX_MISSED) is | |
| # explicit and the failure message is actionable. | |
| cargo mutants --no-times --timeout 300 --in-place \ | |
| --in-diff pr.diff -- --lib | |
| MUT_EXIT=$? | |
| echo "cargo-mutants exit: $MUT_EXIT" | |
| OUTCOMES=mutants.out/outcomes.json | |
| if [ ! -f "$OUTCOMES" ]; then | |
| # No outcomes file means cargo-mutants found no mutants in the | |
| # diff (e.g. diff only touched non-Rust / non-mutable lines). | |
| echo "No mutants.out/outcomes.json — 0 mutants in diff. Pass." | |
| exit 0 | |
| fi | |
| MISSED=$(grep -o "\"summary\"[^}]*\"missed\":[0-9]*" "$OUTCOMES" \ | |
| | grep -o "\"missed\":[0-9]*" | grep -o "[0-9]*" | head -1) | |
| TIMEOUT=$(grep -o "\"timeout\":[0-9]*" "$OUTCOMES" \ | |
| | grep -o "[0-9]*" | head -1) | |
| MISSED=${MISSED:-0}; TIMEOUT=${TIMEOUT:-0} | |
| echo "Diff-scoped mutation result: missed=$MISSED timeout=$TIMEOUT (max allowed missed=$MUTANTS_MAX_MISSED)" | |
| UNCAUGHT=$((MISSED + TIMEOUT)) | |
| if [ "$UNCAUGHT" -gt "$MUTANTS_MAX_MISSED" ]; then | |
| echo "::error::$UNCAUGHT mutant(s) survived/timed-out on the PR diff (> $MUTANTS_MAX_MISSED allowed). New code is under-tested — add tests that kill these mutants. This would have merged SILENTLY before (PMAT gap #1)." | |
| exit 1 | |
| fi | |
| echo "All diff-scoped mutants caught (or within threshold). Pass." | |
| exit 0 | |
| ' | |
| - name: Upload mutation results | |
| if: always() | |
| uses: actions/upload-artifact@v7 | |
| with: | |
| name: mutation-results | |
| path: mutants.out/ |