77# run reads. This file is the caller.
88#
99# WHAT A RED HERE MEANS. Nothing about a change in flight, because no change can
10- # trigger this workflow. A red means either that the alarm did not operate or
11- # that a timed-out mutant left its outcome uncertain. SURVIVING MUTANTS DO NOT
12- # TURN IT RED. They are the finding, they are printed in full, and they are
13- # evidence debt owed at the home of the source that carries them; a job that
14- # went red on the first survivor would be red from the day it landed, and an
15- # alarm that is always red is an alarm nobody reads.
10+ # trigger this workflow. A red means that the alarm did not operate, that a
11+ # timed-out mutant left its outcome uncertain, or that external interruption
12+ # prevented a final observation. SURVIVING MUTANTS DO NOT TURN IT RED. They are
13+ # the finding, they are printed in full, and they are evidence debt owed at the
14+ # home of the source that carries them; a job that went red on the first
15+ # survivor would be red from the day it landed, and an alarm that is always red
16+ # is an alarm nobody reads.
1617name : mutation
1718
1819on :
@@ -34,7 +35,68 @@ concurrency:
3435permissions :
3536 contents : read
3637
38+ env :
39+ CARGO_MUTANTS_VERSION : " 27.0.0"
40+
3741jobs :
42+ # THE PLANTED REVERSAL. This is a sibling of the campaign, with no `needs`
43+ # edge in either direction. A campaign runner that disappears therefore
44+ # cannot erase the cheap observation that the empty scope is still empty.
45+ # Whole-workflow cancellation may stop both jobs; no workflow topology can
46+ # make a running job survive cancellation of its own run.
47+ empty-scope-reversal :
48+ if : github.event_name == 'schedule' || github.event_name == 'workflow_dispatch'
49+ runs-on : ubuntu-latest
50+ # The reversal builds no baseline after its deliberately empty selection.
51+ # This is an operational hang ceiling, not a population claim.
52+ timeout-minutes : 30
53+ steps :
54+ - uses : actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4
55+ with :
56+ persist-credentials : false
57+
58+ - name : Install the pinned toolchain
59+ run : |
60+ rustup toolchain install
61+ rustup show
62+
63+ - name : Install cargo-mutants
64+ run : cargo install cargo-mutants --version "$CARGO_MUTANTS_VERSION" --locked
65+
66+ - name : Record the exact reversal basis
67+ run : |
68+ echo "repository SHA: $(git rev-parse HEAD)"
69+ echo "repository tree: $(git rev-parse HEAD^{tree})"
70+ cargo mutants --version
71+
72+ # The real campaign's positive control cannot establish that its empty-
73+ # scope guard bites: a run that silently selects nothing also exits zero.
74+ # Excluding every source selected by `.cargo/mutants.toml` plants that
75+ # exact defect. cargo-mutants v27 writes an empty inventory and empty
76+ # rosters but no outcomes document, and the typed adapter admits exactly
77+ # that producer posture.
78+ - name : The planted reversal must examine nothing
79+ run : |
80+ mkdir -p target
81+ set +e
82+ cargo mutants --workspace -e 'xtask/**' --output target/mutation-reversal
83+ code=$?
84+ set -e
85+ cargo xtask mutation-report reversal "$code" target/mutation-reversal/mutants.out
86+
87+ # This says only that the reversal's checkout remained clean. It is not
88+ # evidence about the independent campaign runner or its checkout.
89+ - name : The reversal checkout remains clean
90+ if : always()
91+ run : |
92+ dirty=$(git status --porcelain=v2 --untracked-files=all)
93+ if [ -n "$dirty" ]; then
94+ echo "::error::the empty-scope reversal changed Git-visible checkout bytes" >&2
95+ printf '%s\n' "$dirty" >&2
96+ exit 1
97+ fi
98+ echo "the empty-scope reversal left its Git-visible checkout clean"
99+
38100 xtask :
39101 # NAMED BY WHAT IT RUNS ON, never by what it skips, and the triggers above
40102 # already allow nothing else — so this condition is redundant TODAY and is
64126 # decision deferred to whoever resolves next. This is the version MEASURED
65127 # on a working machine when `.cargo/mutants.toml` was written.
66128 - name : Install cargo-mutants
67- run : cargo install cargo-mutants --version 27.0.0 --locked
129+ run : cargo install cargo-mutants --version "$CARGO_MUTANTS_VERSION" --locked
68130
69131 - name : Record the exact evidence basis
70132 run : |
@@ -82,56 +144,27 @@ jobs:
82144 # this step counts what was examined instead of trusting that something
83145 # was.
84146 #
85- # The typed report adapter owns the producer's exit-code and report
86- # contract. In particular, survivors are evidence debt and keep the alarm
87- # green after validation, while a timeout prints its full receipt and then
88- # leaves this step red because the outcome is uncertain.
147+ # The fixed xtask launcher first validates this clean checkout as the
148+ # campaign's exact subject, then passes its absolute root, commit, and tree
149+ # to tests compiled in cargo-mutants' VCS-free scratch copy. It owns the
150+ # producer exit and invokes the typed report adapter afterward. Survivors
151+ # are evidence debt and keep the alarm green after validation, while a
152+ # timeout prints its full receipt and then leaves this step red because
153+ # the outcome is uncertain.
89154 #
90- # `-j 2` builds in two scratch directories at once. More would trade the
91- # runner's disk for a speed this job does not need, since nothing waits on
92- # it.
93155 - name : The mutation run
94- run : |
95- mkdir -p target
96- set +e
97- cargo mutants --workspace -j 2 --output target/mutation-run
98- code=$?
99- set -e
100- cargo xtask mutation-report run "$code" target/mutation-run/mutants.out
101-
102- # THE PLANTED REVERSAL. The step above establishes that the alarm worked
103- # today. It cannot establish that its own guard bites, because a guard that
104- # quietly stopped counting prints the same numbers as one that counted
105- # everything — and what it guards against costs nothing by MEASUREMENT: a
106- # scope matching no source finds no mutant, warns, writes empty rosters,
107- # and exits 0.
108- #
109- # So the same run is made deliberately wrong, by excluding every source the
110- # scope selects, and this step fails the job when that run comes back with
111- # a mutant. It writes to its own report directory so the finding above is
112- # not overwritten by a run that found nothing. MEASURED: it finishes in
113- # seconds, because a run with nothing to test builds no baseline.
114- #
115- # WHAT THIS COVERS: that an empty scope really is silent, and that a run
116- # over one is distinguishable from a run over the real one by the typed
117- # report contract both roads consume.
118- - name : The planted reversal must examine nothing
119- if : always()
120- run : |
121- mkdir -p target
122- set +e
123- cargo mutants --workspace -e 'xtask/**' --output target/mutation-reversal
124- code=$?
125- set -e
126- cargo xtask mutation-report reversal "$code" target/mutation-reversal/mutants.out
156+ run : cargo xtask mutation-campaign
127157
128- - name : The checkout remains clean
158+ # This can run after an ordinary command refusal. It cannot run after the
159+ # hosted runner itself disappears, so cleanliness is claimed only when
160+ # this step has an executed record.
161+ - name : The campaign checkout remains clean
129162 if : always()
130163 run : |
131164 dirty=$(git status --porcelain=v2 --untracked-files=all)
132165 if [ -n "$dirty" ]; then
133- echo "::error::the mutation alarm changed Git-visible checkout bytes" >&2
166+ echo "::error::the mutation campaign changed Git-visible checkout bytes" >&2
134167 printf '%s\n' "$dirty" >&2
135168 exit 1
136169 fi
137- echo "the mutation alarm left the Git-visible checkout clean"
170+ echo "the mutation campaign left its Git-visible checkout clean"
0 commit comments