@@ -15,11 +15,9 @@ name: Build & Tests
1515 - main
1616 pull_request :
1717 merge_group :
18- # Useful for manually testing changes to this workflow. Because we check for
19- # the merge queue by doing `github.event_name != 'pull_request'` rather than
20- # `github.event_name == 'merge_group'`, triggering via workflow dispatch has
21- # the effect of running all jobs, including those which would normally only be
22- # run in the merge queue.
18+ # Useful for manually testing changes to this workflow. The typed planner
19+ # classifies this as a full event, so its plan enables all ordinary CI work,
20+ # including Miri.
2321 workflow_dispatch :
2422
2523permissions :
6159 # jobs consume only the checked selectors from each matrix cell and pass them
6260 # back to `cargo-zerocopy`, which reconstructs and executes the complete
6361 # command without interpreting matrix data as shell text. Keep the output
64- # names coordinated with `tools/zc/src/github.rs` and the two `fromJSON`
65- # expressions below.
62+ # names coordinated with `tools/zc/src/github.rs`, the two `fromJSON`
63+ # expressions below, and the Miri eligibility consumers below .
6664 plan_ci :
6765 name : Plan ordinary CI work
6866 runs-on : ubuntu-latest
7169 outputs :
7270 build_matrix : ${{ steps.plan.outputs.build_matrix }}
7371 miri_matrix : ${{ steps.plan.outputs.miri_matrix }}
74- defaults :
75- run :
76- working-directory : zerocopy
72+ # This gate is derived from the projected Miri matrix, not independently
73+ # from the event name. Keep it coordinated with `tools/zc/src/github.rs`,
74+ # the Miri job condition, and the required-check aggregation.
75+ miri_enabled : ${{ steps.plan.outputs.miri_enabled }}
7776 env :
7877 # upload-file-artifact requires its name to equal the path basename.
7978 # Keep this one value coordinated with the planner invocation and upload
@@ -86,11 +85,21 @@ jobs:
8685
8786 - name : Validate inputs and project the plan
8887 id : plan
88+ # The absolute custom shell removes Bash startup-control variables and
89+ # enables privileged mode so inherited startup files, shell options,
90+ # and exported functions cannot turn this step into a successful no-op.
91+ shell : /usr/bin/env -u BASH_ENV -u ENV -u SHELLOPTS -u BASHOPTS /bin/bash --noprofile --norc -p -euo pipefail -- {0}
92+ working-directory : zerocopy
8993 env :
9094 EVENT_NAME : ${{ github.event_name }}
95+ # The runner applies GITHUB_PATH after merging step env, so assign the
96+ # fixed path directly to the child command. Do not rely on cargo.sh's
97+ # /usr/bin/env bash shebang. Privileged mode also prevents an imported
98+ # function from replacing a builtin when this child Bash starts.
9199 run : |
92100 set -euo pipefail
93- ./cargo.sh ci github-plan \
101+ PATH=/home/runner/.cargo/bin:/usr/local/bin:/usr/bin:/bin \
102+ /bin/bash --noprofile --norc -p ./cargo.sh ci github-plan \
94103 --event "$EVENT_NAME" \
95104 --github-output "$GITHUB_OUTPUT" \
96105 --artifact "$RUNNER_TEMP/$CI_PLAN_ARTIFACT"
@@ -213,18 +222,48 @@ jobs:
213222
214223 # The matrix values are data, not shell fragments. `cargo-zerocopy`
215224 # validates these selectors against the plan for this event, reconstructs
216- # the typed argv and environment, and executes the complete cell. Keep this
217- # invocation coordinated with `tools/zc/src/cli.rs`; adding a command here
218- # would bypass the checked execution model.
225+ # the typed argv and environment, and executes the complete cell. This step
226+ # deliberately does not use the job's generated Docker shell: auditing its
227+ # path would not prove that the generated wrapper still invokes Docker.
228+ # Instead, the complete bridge below is explicit. The absolute Docker path
229+ # relies on the hosted Ubuntu runner contract and prevents a PATH shim from
230+ # returning success without starting a container. The entrypoint override
231+ # and Bash startup options prevent image startup behavior or exported
232+ # functions from intercepting the typed executor argv. The option
233+ # terminator forces the inherited image value to be parsed as an image.
234+ # Keep this command and its options coordinated with `tools/zc/src/cli.rs`.
219235 - name : Execute checked build cell
236+ shell : /usr/bin/env -u BASH_ENV -u ENV -u SHELLOPTS -u BASHOPTS /bin/bash --noprofile --norc -p -euo pipefail -- {0}
237+ working-directory : zerocopy
220238 env :
221239 TOOLCHAIN : ${{ matrix.toolchain }}
222240 CRATE : ${{ matrix.crate }}
223241 FEATURE_PROFILE : ${{ matrix.feature_profile }}
224242 TARGET : ${{ matrix.target }}
225243 run : |
226244 set -euo pipefail
227- ./cargo.sh ci execute-build-cell \
245+ /usr/bin/docker run --rm \
246+ --workdir "$PWD" \
247+ -v /home/runner/work:/home/runner/work \
248+ -v /home/runner/.docker-cargo/registry:/root/.cargo/registry \
249+ -v /home/runner/.docker-cargo/git:/root/.cargo/git \
250+ -e GITHUB_ENV -e GITHUB_PATH -e GITHUB_STEP_SUMMARY -e GITHUB_OUTPUT -e GITHUB_WORKSPACE \
251+ -e CI -e GITHUB_ACTIONS -e GITHUB_ACTOR -e GITHUB_REPOSITORY -e GITHUB_SHA -e GITHUB_REF -e GITHUB_EVENT_NAME \
252+ -e TOOLCHAIN -e CRATE -e TARGET -e FEATURE_PROFILE \
253+ -e RUSTFLAGS -e RUSTDOCFLAGS -e MIRIFLAGS \
254+ -e CARGO_NET_RETRY -e RUSTUP_MAX_RETRIES \
255+ -e ZC_NIGHTLY_RUSTFLAGS -e ZC_NIGHTLY_MIRIFLAGS \
256+ -e ZC_SKIP_CARGO_SEMVER_CHECKS \
257+ -e GIT_CONFIG_COUNT=1 \
258+ -e GIT_CONFIG_KEY_0=safe.directory \
259+ -e "GIT_CONFIG_VALUE_0=*" \
260+ --entrypoint /bin/bash \
261+ -- \
262+ "$ZC_CI_IMAGE" \
263+ --noprofile \
264+ --norc \
265+ -p \
266+ ./cargo.sh ci execute-build-cell \
228267 --event "$GITHUB_EVENT_NAME" \
229268 --package "$CRATE" \
230269 --toolchain "$TOOLCHAIN" \
@@ -319,24 +358,26 @@ jobs:
319358 matrix.target != 'wasm32-unknown-unknown' &&
320359 env.ZC_SKIP_CARGO_SEMVER_CHECKS != '1'
321360
322- # Miri runs only on full events because it is much more expensive than the
323- # ordinary build matrix. Each borrow model is its own matrix cell so the two
324- # models and all eligible target/profile combinations can run concurrently.
361+ # Today's policy selects Miri only for full events because it is much more
362+ # expensive than the ordinary build matrix. Each borrow model is its own
363+ # matrix cell so the models and all eligible target/profile combinations can
364+ # run concurrently. The job itself follows the plan rather than repeating
365+ # that current policy.
325366 miri :
326- if : github.event_name != 'pull_request'
367+ # The planner derives this output solely from whether it selected any Miri
368+ # cells. Do not duplicate today's PR/full-event policy here: future policy
369+ # changes must automatically enable or skip this job with the matrix they
370+ # produce.
371+ if : needs.plan_ci.outputs.miri_enabled == 'true'
327372 runs-on : ubuntu-latest
328373 needs : [build_docker_env, plan_ci]
329374 permissions :
330375 contents : read
331- defaults :
332- run :
333- shell : /tmp/docker-shell.sh {0} # zizmor: ignore[misfeature] (CI intentionally routes Miri through the prebuilt Docker image)
334- working-directory : zerocopy
335376 strategy :
336377 fail-fast : false
337- # Pull requests skip this job before matrix expansion. Full events consume
338- # exactly the Miri cells selected by the same checked plan used by the
339- # executor below.
378+ # A planner-disabled job skips before matrix expansion. When enabled, it
379+ # consumes exactly the Miri cells selected by the same checked plan used
380+ # by the executor below.
340381 matrix : ${{ fromJSON(needs.plan_ci.outputs.miri_matrix) }}
341382
342383 name : Miri (${{ matrix.crate }} / ${{ matrix.feature_profile }} / ${{ matrix.miri_model }} / ${{ matrix.target }})
@@ -345,12 +386,18 @@ jobs:
345386 - *matrix_checkout
346387 - *download_ci_image
347388 - *load_ci_image
348- - *create_docker_shell
349389
350390 # As with ordinary cells, the workflow passes only selectors. Model flags,
351391 # feature arguments, the Cargo configuration transaction, and target
352392 # workarounds belong to the typed executor rather than this shell adapter.
393+ # This explicit Docker bridge does not rely on the generated job shell
394+ # actually executing its input. Its absolute Docker path, entrypoint
395+ # override, Bash startup options, and option terminator provide the same
396+ # fail-closed boundary as the ordinary build bridge. Keep the separate
397+ # Miri-model selector coordinated with `tools/zc/src/cli.rs`.
353398 - name : Execute checked Miri cell
399+ shell : /usr/bin/env -u BASH_ENV -u ENV -u SHELLOPTS -u BASHOPTS /bin/bash --noprofile --norc -p -euo pipefail -- {0}
400+ working-directory : zerocopy
354401 env :
355402 TOOLCHAIN : ${{ matrix.toolchain }}
356403 CRATE : ${{ matrix.crate }}
@@ -359,7 +406,28 @@ jobs:
359406 MIRI_MODEL : ${{ matrix.miri_model }}
360407 run : |
361408 set -euo pipefail
362- ./cargo.sh ci execute-miri-cell \
409+ /usr/bin/docker run --rm \
410+ --workdir "$PWD" \
411+ -v /home/runner/work:/home/runner/work \
412+ -v /home/runner/.docker-cargo/registry:/root/.cargo/registry \
413+ -v /home/runner/.docker-cargo/git:/root/.cargo/git \
414+ -e GITHUB_ENV -e GITHUB_PATH -e GITHUB_STEP_SUMMARY -e GITHUB_OUTPUT -e GITHUB_WORKSPACE \
415+ -e CI -e GITHUB_ACTIONS -e GITHUB_ACTOR -e GITHUB_REPOSITORY -e GITHUB_SHA -e GITHUB_REF -e GITHUB_EVENT_NAME \
416+ -e TOOLCHAIN -e CRATE -e TARGET -e FEATURE_PROFILE -e MIRI_MODEL \
417+ -e RUSTFLAGS -e RUSTDOCFLAGS -e MIRIFLAGS \
418+ -e CARGO_NET_RETRY -e RUSTUP_MAX_RETRIES \
419+ -e ZC_NIGHTLY_RUSTFLAGS -e ZC_NIGHTLY_MIRIFLAGS \
420+ -e ZC_SKIP_CARGO_SEMVER_CHECKS \
421+ -e GIT_CONFIG_COUNT=1 \
422+ -e GIT_CONFIG_KEY_0=safe.directory \
423+ -e "GIT_CONFIG_VALUE_0=*" \
424+ --entrypoint /bin/bash \
425+ -- \
426+ "$ZC_CI_IMAGE" \
427+ --noprofile \
428+ --norc \
429+ -p \
430+ ./cargo.sh ci execute-miri-cell \
363431 --event "$GITHUB_EVENT_NAME" \
364432 --package "$CRATE" \
365433 --toolchain "$TOOLCHAIN" \
@@ -768,32 +836,39 @@ jobs:
768836
769837 # GitHub can omit a job output at promotion time if its secret scanner
770838 # produces a false positive. Keep this comparison in expression space
771- # so the large JSON never enters a process environment; a PR's empty
772- # Miri plan is still the nonempty JSON value {"include":[]}.
839+ # so the large JSON never enters a process environment. A disabled Miri
840+ # plan is still the nonempty JSON value {"include":[]}; the boolean gate
841+ # must also be present and canonical before aggregation trusts it.
773842 - name : Require published planner outputs
774- if : ${{ needs.plan_ci.result == 'success' && (needs.plan_ci.outputs.build_matrix == '' || needs.plan_ci.outputs.miri_matrix == '') }}
843+ if : ${{ needs.plan_ci.result == 'success' && (needs.plan_ci.outputs.build_matrix == '' || needs.plan_ci.outputs.miri_matrix == '' || (needs.plan_ci.outputs.miri_enabled != 'true' && needs.plan_ci.outputs.miri_enabled != 'false')) }}
844+ shell : /usr/bin/env -u BASH_ENV -u ENV -u SHELLOPTS -u BASHOPTS /bin/bash --noprofile --norc -p -euo pipefail -- {0}
775845 run : exit 1
776846
777847 - name : Require every dependency to succeed
848+ # Pin the absolute interpreter and remove Bash startup controls so a
849+ # job default, PATH shim, or exported function cannot turn this
850+ # required-check assertion into a successful no-op.
851+ shell : /usr/bin/env -u BASH_ENV -u ENV -u SHELLOPTS -u BASHOPTS /bin/bash --noprofile --norc -p -euo pipefail -- {0}
778852 env :
779- EVENT_NAME : ${{ github.event_name }}
780853 # Do not serialize the entire `needs` object here: job outputs may
781854 # legitimately approach GitHub's configured output limit, while a
782855 # Linux process has a much smaller per-environment-value limit.
783856 # Results stay small regardless of matrix JSON size. Keep the
784- # separate Miri result so the one intentional PR skip is still
785- # identified rather than accepting an arbitrary skipped job .
857+ # separate Miri result so a planner-disabled Miri job is identified
858+ # rather than accepting an arbitrary skipped dependency .
786859 RESULTS_JSON : ${{ toJSON(needs.*.result) }}
860+ MIRI_ENABLED : ${{ needs.plan_ci.outputs.miri_enabled }}
787861 MIRI_RESULT : ${{ needs.miri.result }}
788862 run : |
789863 set -euo pipefail
790- jq -e --arg event "$EVENT_NAME " --arg miri "$MIRI_RESULT" '
864+ /usr/bin/ jq -e --arg enabled "$MIRI_ENABLED " --arg miri "$MIRI_RESULT" '
791865 type == "array" and length > 0 and
792- if $event == "pull_request "
866+ if $enabled == "false "
793867 then $miri == "skipped" and
794868 ([.[] | select(. == "skipped")] | length) == 1 and
795869 all(.[]; . == "success" or . == "skipped")
796- else $miri == "success" and
870+ else $enabled == "true" and
871+ $miri == "success" and
797872 all(.[]; . == "success")
798873 end
799874 ' <<< "$RESULTS_JSON"
0 commit comments