diff --git a/.github/workflows/build.yaml b/.github/workflows/build.yaml index 86dfbb066c..ca95c2fe2f 100644 --- a/.github/workflows/build.yaml +++ b/.github/workflows/build.yaml @@ -89,11 +89,19 @@ jobs: git -C source check-ref-format --branch "$D2B_BRANCH" >/dev/null 2>&1 || fail "branch metadata is not an approved ref" + validate_optional_reusable_workflow() { + if [ -n "$D2B_JOB_WORKFLOW_REF" ] || [ -n "$D2B_JOB_WORKFLOW_SHA" ]; then + [ "$D2B_JOB_WORKFLOW_REF" = "vicondoa/d2b/.github/workflows/build.yaml@refs/heads/main" ] || + fail "reusable build controls must be sourced from protected main" + is_sha "$D2B_JOB_WORKFLOW_SHA" || + fail "reusable workflow SHA is not a full immutable commit SHA" + fi + } + if [ "$D2B_EVENT_NAME" = "pull_request_target" ]; then [ "$D2B_CALLER_WORKFLOW_REF" = "vicondoa/d2b/.github/workflows/pr.yml@refs/heads/main" ] || fail "PR workflow controls must be sourced from protected main" - [ "$D2B_JOB_WORKFLOW_REF" = "vicondoa/d2b/.github/workflows/build.yaml@refs/heads/main" ] || - fail "reusable build controls must be sourced from protected main" + validate_optional_reusable_workflow case "$D2B_BASE_REF" in main|v3) ;; *) fail "pull request target is not main or v3" ;; @@ -110,6 +118,7 @@ jobs: elif [ "$D2B_EVENT_NAME" = "push" ]; then [ "$D2B_CALLER_WORKFLOW_REF" = "vicondoa/d2b/.github/workflows/build.yaml@refs/heads/main" ] || fail "push workflow controls must be sourced from protected main" + validate_optional_reusable_workflow [ "$D2B_REF" = "refs/heads/main" ] || fail "trusted build pushes are restricted to main" [ -z "$D2B_BASE_REF" ] || fail "push events must not carry a base ref" @@ -202,6 +211,23 @@ jobs: set -euo pipefail test "$(git -C trusted rev-parse HEAD)" = "$D2B_TRUSTED_SHA" test "$(git -C workspace rev-parse HEAD)" = "$D2B_TESTED_SHA" + - name: Prepare pinned Rust toolchain for parallel Layer-1 gates + working-directory: workspace + run: | + set -euo pipefail + if ! command -v rustup >/dev/null 2>&1; then + echo "rustup is unavailable; Rust gates will use their Nix fallback." + exit 0 + fi + pinned_channel="$( + sed -n 's/^[[:space:]]*channel[[:space:]]*=[[:space:]]*"\([^"]*\)".*/\1/p' \ + packages/rust-toolchain.toml | head -1 + )" + [ -n "$pinned_channel" ] || { + echo "::error::could not read the pinned Rust channel" >&2 + exit 76 + } + rustup toolchain install "$pinned_channel" --profile minimal --component rustfmt --component clippy - name: Run credential-free local Layer-1 gate working-directory: workspace run: | diff --git a/CHANGELOG.md b/CHANGELOG.md index 19e54b4986..f150a29b06 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -47,6 +47,11 @@ deprecations ship one minor release before removal. - Fixed trusted `main` push validation and reusable workflow resolution so the BuildBuddy seed workflow executes its metadata checks instead of failing on shell test parsing. +- Accepted GitHub runs that omit `job_workflow_ref` and `job_workflow_sha` for + same-repository reusable workflows while still validating those immutable + identifiers whenever GitHub provides them. +- Prepared the pinned Rust toolchain before parallel local Layer-1 gates so + concurrent Rust and proof jobs do not race during rustup installation. ## [1.4.1] - 2026-07-12 diff --git a/packages/xtask/tests/policy_ci.rs b/packages/xtask/tests/policy_ci.rs index b50f4362a9..0c2200060a 100644 --- a/packages/xtask/tests/policy_ci.rs +++ b/packages/xtask/tests/policy_ci.rs @@ -170,6 +170,43 @@ fn main_controlled_buildbuddy_workflows_preserve_trust_contract() { && build.contains("merge_sha"), "build workflow must bind trusted and tested immutable OIDs" ); + assert!( + build.contains("trusted_sha=\"${D2B_JOB_WORKFLOW_SHA:-$D2B_CALLER_WORKFLOW_SHA}\""), + "trusted workflow SHA must fall back to the caller SHA when job metadata is absent" + ); + let helper_start = build + .find(" validate_optional_reusable_workflow() {") + .expect("build workflow must define optional reusable-workflow validation"); + let pr_validation_start = build + .find(" if [ \"$D2B_EVENT_NAME\" = \"pull_request_target\" ]") + .expect("build workflow must validate pull_request_target metadata"); + let push_validation_start = build + .find(" elif [ \"$D2B_EVENT_NAME\" = \"push\" ]") + .expect("build workflow must validate push metadata"); + let helper_end = helper_start + + build[helper_start..pr_validation_start] + .find("\n }\n\n") + .expect("optional reusable-workflow validator must end before event validation"); + let helper = &build[helper_start..helper_end]; + assert!( + helper.contains( + "if [ -n \"$D2B_JOB_WORKFLOW_REF\" ] || [ -n \"$D2B_JOB_WORKFLOW_SHA\" ]; then" + ) && helper.contains( + "[ \"$D2B_JOB_WORKFLOW_REF\" = \"vicondoa/d2b/.github/workflows/build.yaml@refs/heads/main\" ]" + ) && helper.contains("is_sha \"$D2B_JOB_WORKFLOW_SHA\""), + "optional reusable-workflow metadata checks must stay inside their validator" + ); + let push_validation_end = push_validation_start + + build[push_validation_start..] + .find("\n else\n fail \"only pull_request_target and push events are accepted\"") + .expect("build workflow event validation must have a bounded fallback branch"); + assert!( + build[pr_validation_start..push_validation_start] + .contains(" validate_optional_reusable_workflow\n") + && build[push_validation_start..push_validation_end] + .contains(" validate_optional_reusable_workflow\n"), + "optional reusable-workflow metadata validation must bind to both event paths" + ); assert!( build.matches("persist-credentials: false").count() >= 4, "trusted and source checkouts must not persist credentials" @@ -211,6 +248,30 @@ fn main_controlled_buildbuddy_workflows_preserve_trust_contract() { && build.contains("D2B_BAZEL_UNTRUSTED: \"1\""), "full PR coverage must retain a credential-free local Layer-1 gate" ); + let local_job_start = build + .find("\n local:\n") + .expect("build workflow must define a local job"); + let local_job_end = local_job_start + + build[local_job_start..] + .find("\n remote:\n") + .expect("build workflow must define a remote job"); + let local_job = &build[local_job_start..local_job_end]; + let rust_bootstrap = local_job + .find(" - name: Prepare pinned Rust toolchain for parallel Layer-1 gates") + .expect("local Layer-1 gate must prepare the shared Rust toolchain"); + let local_gate = local_job + .find(" - name: Run credential-free local Layer-1 gate") + .expect("local Layer-1 gate step must be present"); + let rust_bootstrap_step = &local_job[rust_bootstrap..local_gate]; + assert!( + rust_bootstrap < local_gate + && rust_bootstrap_step.contains("working-directory: workspace") + && rust_bootstrap_step.contains("packages/rust-toolchain.toml") + && rust_bootstrap_step + .contains("rustup toolchain install \"$pinned_channel\" --profile minimal") + && rust_bootstrap_step.contains("--component rustfmt --component clippy"), + "parallel local Layer-1 jobs must share a preinstalled pinned Rust toolchain" + ); assert!( build.contains("github.event_name == 'push'") && build.contains("github.event_name == 'push' }}"),