From 8b9a14ea26b34cb06aad6d663d84a4d0eadc7658 Mon Sep 17 00:00:00 2001 From: John Vicondoa Date: Sat, 22 Aug 2026 12:50:33 -0700 Subject: [PATCH 01/13] ci: preinstall Rust before parallel build gates Prepare the pinned toolchain and required components before make check fans out its local Layer-1 jobs. This avoids concurrent rustup downloads corrupting the shared runner installation. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .github/workflows/build.yaml | 17 +++++++++++++++++ CHANGELOG.md | 2 ++ packages/xtask/tests/policy_ci.rs | 12 ++++++++++++ 3 files changed, 31 insertions(+) diff --git a/.github/workflows/build.yaml b/.github/workflows/build.yaml index 86dfbb066c..01cdec15c4 100644 --- a/.github/workflows/build.yaml +++ b/.github/workflows/build.yaml @@ -202,6 +202,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..167ca699bc 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -47,6 +47,8 @@ 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. +- 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..6520fb64f0 100644 --- a/packages/xtask/tests/policy_ci.rs +++ b/packages/xtask/tests/policy_ci.rs @@ -211,6 +211,18 @@ 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 rust_bootstrap = build + .find("Prepare pinned Rust toolchain for parallel Layer-1 gates") + .expect("local Layer-1 gate must prepare the shared Rust toolchain"); + let local_gate = build + .find("Run credential-free local Layer-1 gate") + .expect("local Layer-1 gate step must be present"); + assert!( + rust_bootstrap < local_gate + && build.contains("rustup toolchain install \"$pinned_channel\" --profile minimal") + && build.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' }}"), From 680f703e4b0bfe8b8c38dba1cadaabafca255fa4 Mon Sep 17 00:00:00 2001 From: John Vicondoa Date: Sat, 22 Aug 2026 13:13:24 -0700 Subject: [PATCH 02/13] ci: bind Rust bootstrap policy to local gate Scope the workflow contract to the local job and require the workspace checkout and pinned toolchain install in the step preceding make check. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- packages/xtask/tests/policy_ci.rs | 24 ++++++++++++++++++------ 1 file changed, 18 insertions(+), 6 deletions(-) diff --git a/packages/xtask/tests/policy_ci.rs b/packages/xtask/tests/policy_ci.rs index 6520fb64f0..0363c84862 100644 --- a/packages/xtask/tests/policy_ci.rs +++ b/packages/xtask/tests/policy_ci.rs @@ -211,16 +211,28 @@ 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 rust_bootstrap = build - .find("Prepare pinned Rust toolchain for parallel Layer-1 gates") + 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 = build - .find("Run credential-free local Layer-1 gate") + 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 - && build.contains("rustup toolchain install \"$pinned_channel\" --profile minimal") - && build.contains("--component rustfmt --component clippy"), + && 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!( From 81c2b625ee4867bdc6cbec8d60e4b13b8f627c57 Mon Sep 17 00:00:00 2001 From: John Vicondoa Date: Sat, 22 Aug 2026 13:36:09 -0700 Subject: [PATCH 03/13] ci: tolerate absent reusable workflow context Validate optional reusable workflow identifiers when GitHub provides them, while retaining the caller workflow SHA fallback for same-repository calls. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .github/workflows/build.yaml | 13 +++++++++++-- CHANGELOG.md | 3 +++ packages/xtask/tests/policy_ci.rs | 8 ++++++++ 3 files changed, 22 insertions(+), 2 deletions(-) diff --git a/.github/workflows/build.yaml b/.github/workflows/build.yaml index 01cdec15c4..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" diff --git a/CHANGELOG.md b/CHANGELOG.md index 167ca699bc..f150a29b06 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -47,6 +47,9 @@ 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. diff --git a/packages/xtask/tests/policy_ci.rs b/packages/xtask/tests/policy_ci.rs index 0363c84862..11c48f3bce 100644 --- a/packages/xtask/tests/policy_ci.rs +++ b/packages/xtask/tests/policy_ci.rs @@ -170,6 +170,14 @@ 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}\"") + && build.contains( + "if [ -n \"$D2B_JOB_WORKFLOW_REF\" ] || [ -n \"$D2B_JOB_WORKFLOW_SHA\" ]; then" + ) + && build.contains("is_sha \"$D2B_JOB_WORKFLOW_SHA\""), + "optional reusable-workflow metadata must be validated when GitHub provides it" + ); assert!( build.matches("persist-credentials: false").count() >= 4, "trusted and source checkouts must not persist credentials" From 8696ddd430524ddfb18addbc66da85c1ea6fba28 Mon Sep 17 00:00:00 2001 From: John Vicondoa Date: Sat, 22 Aug 2026 14:06:54 -0700 Subject: [PATCH 04/13] ci: bind optional workflow metadata policy Require the protected workflow ref and both event-path call sites in the policy contract. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- packages/xtask/tests/policy_ci.rs | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/packages/xtask/tests/policy_ci.rs b/packages/xtask/tests/policy_ci.rs index 11c48f3bce..86f0c534da 100644 --- a/packages/xtask/tests/policy_ci.rs +++ b/packages/xtask/tests/policy_ci.rs @@ -175,9 +175,24 @@ fn main_controlled_buildbuddy_workflows_preserve_trust_contract() { && build.contains( "if [ -n \"$D2B_JOB_WORKFLOW_REF\" ] || [ -n \"$D2B_JOB_WORKFLOW_SHA\" ]; then" ) + && build.contains( + "[ \"$D2B_JOB_WORKFLOW_REF\" = \"vicondoa/d2b/.github/workflows/build.yaml@refs/heads/main\" ]" + ) && build.contains("is_sha \"$D2B_JOB_WORKFLOW_SHA\""), "optional reusable-workflow metadata must be validated when GitHub provides it" ); + 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"); + assert!( + build[pr_validation_start..push_validation_start] + .contains("validate_optional_reusable_workflow") + && build[push_validation_start..].contains("validate_optional_reusable_workflow"), + "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" From fd86f255c7bb4bc11b771396ab7b79df07cc8ec0 Mon Sep 17 00:00:00 2001 From: John Vicondoa Date: Sat, 22 Aug 2026 14:33:05 -0700 Subject: [PATCH 05/13] ci: bind workflow policy slices Keep optional metadata checks inside their helper and bind both event-path calls to the event switch. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- packages/xtask/tests/policy_ci.rs | 40 +++++++++++++++++++++---------- 1 file changed, 27 insertions(+), 13 deletions(-) diff --git a/packages/xtask/tests/policy_ci.rs b/packages/xtask/tests/policy_ci.rs index 86f0c534da..0c2200060a 100644 --- a/packages/xtask/tests/policy_ci.rs +++ b/packages/xtask/tests/policy_ci.rs @@ -171,26 +171,40 @@ fn main_controlled_buildbuddy_workflows_preserve_trust_contract() { "build workflow must bind trusted and tested immutable OIDs" ); assert!( - build.contains("trusted_sha=\"${D2B_JOB_WORKFLOW_SHA:-$D2B_CALLER_WORKFLOW_SHA}\"") - && build.contains( - "if [ -n \"$D2B_JOB_WORKFLOW_REF\" ] || [ -n \"$D2B_JOB_WORKFLOW_SHA\" ]; then" - ) - && build.contains( - "[ \"$D2B_JOB_WORKFLOW_REF\" = \"vicondoa/d2b/.github/workflows/build.yaml@refs/heads/main\" ]" - ) - && build.contains("is_sha \"$D2B_JOB_WORKFLOW_SHA\""), - "optional reusable-workflow metadata must be validated when GitHub provides it" + 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\" ]") + .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\" ]") + .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") - && build[push_validation_start..].contains("validate_optional_reusable_workflow"), + .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!( From 5a5ba496b8b0215fe3c187e46944a468cac408ab Mon Sep 17 00:00:00 2001 From: John Vicondoa Date: Sat, 22 Aug 2026 15:27:37 -0700 Subject: [PATCH 06/13] ci: serialize hosted build gate Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .github/workflows/build.yaml | 5 +++++ CHANGELOG.md | 3 +++ packages/xtask/tests/policy_ci.rs | 4 ++++ 3 files changed, 12 insertions(+) diff --git a/.github/workflows/build.yaml b/.github/workflows/build.yaml index ca95c2fe2f..ec9ef8ac10 100644 --- a/.github/workflows/build.yaml +++ b/.github/workflows/build.yaml @@ -183,6 +183,11 @@ jobs: D2B_BAZEL_PROFILE: local D2B_BAZEL_UNTRUSTED: "1" D2B_BAZEL_REQUIRE_REMOTE: "0" + # The build job also runs the nested local flake shards. Keep both + # layers serial on the hosted runner so Nix evaluation and Rust + # compilation cannot exhaust its memory before the gate reports. + D2B_CHECK_JOBS: "1" + D2B_FLAKE_JOBS: "1" steps: - name: Checkout immutable trusted controls uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 diff --git a/CHANGELOG.md b/CHANGELOG.md index f150a29b06..d25fb838d0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -52,6 +52,9 @@ deprecations ship one minor release before removal. 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. +- Serialized the main push workflow's nested local Layer-1 fan-out on hosted + runners so concurrent Nix evaluation and Rust compilation cannot exhaust the + runner before the aggregate check reports. ## [1.4.1] - 2026-07-12 diff --git a/packages/xtask/tests/policy_ci.rs b/packages/xtask/tests/policy_ci.rs index 0c2200060a..28c27050be 100644 --- a/packages/xtask/tests/policy_ci.rs +++ b/packages/xtask/tests/policy_ci.rs @@ -256,6 +256,10 @@ fn main_controlled_buildbuddy_workflows_preserve_trust_contract() { .find("\n remote:\n") .expect("build workflow must define a remote job"); let local_job = &build[local_job_start..local_job_end]; + assert!( + local_job.contains("D2B_CHECK_JOBS: \"1\"") && local_job.contains("D2B_FLAKE_JOBS: \"1\""), + "main hosted local gate must serialize nested Layer-1 fan-out" + ); 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"); From 554b60f47eba164e6ab3bf2ad91debcef90e05ec Mon Sep 17 00:00:00 2001 From: John Vicondoa Date: Sat, 22 Aug 2026 15:59:43 -0700 Subject: [PATCH 07/13] test: anchor hosted concurrency policy Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- packages/xtask/tests/policy_ci.rs | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/packages/xtask/tests/policy_ci.rs b/packages/xtask/tests/policy_ci.rs index 28c27050be..ce14177fbe 100644 --- a/packages/xtask/tests/policy_ci.rs +++ b/packages/xtask/tests/policy_ci.rs @@ -256,9 +256,18 @@ fn main_controlled_buildbuddy_workflows_preserve_trust_contract() { .find("\n remote:\n") .expect("build workflow must define a remote job"); let local_job = &build[local_job_start..local_job_end]; + let local_env_start = local_job + .find("\n env:\n") + .expect("local job must define a job-level environment"); + let local_env_end = local_env_start + + local_job[local_env_start..] + .find("\n steps:\n") + .expect("local job environment must end before its steps"); + let local_env = &local_job[local_env_start..local_env_end]; assert!( - local_job.contains("D2B_CHECK_JOBS: \"1\"") && local_job.contains("D2B_FLAKE_JOBS: \"1\""), - "main hosted local gate must serialize nested Layer-1 fan-out" + local_env.contains("\n D2B_CHECK_JOBS: \"1\"\n") + && local_env.contains("\n D2B_FLAKE_JOBS: \"1\"\n"), + "main hosted local gate must export serialized Layer-1 bounds at job scope" ); let rust_bootstrap = local_job .find(" - name: Prepare pinned Rust toolchain for parallel Layer-1 gates") From 76b75e1f59751c182f0fc410184e9939bd47b751 Mon Sep 17 00:00:00 2001 From: John Vicondoa Date: Sat, 22 Aug 2026 16:14:23 -0700 Subject: [PATCH 08/13] test: fix hosted concurrency policy assertion Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- packages/xtask/tests/policy_ci.rs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/packages/xtask/tests/policy_ci.rs b/packages/xtask/tests/policy_ci.rs index ce14177fbe..dd2544cac8 100644 --- a/packages/xtask/tests/policy_ci.rs +++ b/packages/xtask/tests/policy_ci.rs @@ -264,9 +264,10 @@ fn main_controlled_buildbuddy_workflows_preserve_trust_contract() { .find("\n steps:\n") .expect("local job environment must end before its steps"); let local_env = &local_job[local_env_start..local_env_end]; + let local_env_lines = local_env.lines().collect::>(); assert!( - local_env.contains("\n D2B_CHECK_JOBS: \"1\"\n") - && local_env.contains("\n D2B_FLAKE_JOBS: \"1\"\n"), + local_env_lines.contains(&" D2B_CHECK_JOBS: \"1\"") + && local_env_lines.contains(&" D2B_FLAKE_JOBS: \"1\""), "main hosted local gate must export serialized Layer-1 bounds at job scope" ); let rust_bootstrap = local_job From 7be1190a4e8ee514b6e9dd85819d1fe49ac1d8e9 Mon Sep 17 00:00:00 2001 From: John Vicondoa Date: Sat, 22 Aug 2026 17:43:51 -0700 Subject: [PATCH 09/13] ci: refresh pull request merge ref Request a fresh pull_request_target event after GitHub regenerated the tested merge ref. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> From 5bc1dd8d4d18ce2fefa69c8ce6ac4fd313ffa9ec Mon Sep 17 00:00:00 2001 From: John Vicondoa Date: Sat, 22 Aug 2026 17:54:50 -0700 Subject: [PATCH 10/13] fix(ci): validate live pull request merge refs Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .github/workflows/build.yaml | 24 ++++++++++++++++-------- CHANGELOG.md | 3 +++ packages/xtask/tests/policy_ci.rs | 8 ++++++-- 3 files changed, 25 insertions(+), 10 deletions(-) diff --git a/.github/workflows/build.yaml b/.github/workflows/build.yaml index ec9ef8ac10..30fc1a4898 100644 --- a/.github/workflows/build.yaml +++ b/.github/workflows/build.yaml @@ -38,7 +38,7 @@ jobs: - name: Checkout immutable tested revision uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 with: - ref: ${{ github.event_name == 'push' && github.sha || github.event.pull_request.merge_commit_sha }} + ref: ${{ github.event_name == 'push' && github.sha || format('refs/pull/{0}/merge', github.event.pull_request.number) }} path: source fetch-depth: 0 persist-credentials: false @@ -51,7 +51,6 @@ jobs: D2B_DEFAULT_BRANCH: ${{ github.event.repository.default_branch }} D2B_BASE_SHA: ${{ github.event.pull_request.base.sha || github.sha }} D2B_HEAD_SHA: ${{ github.event.pull_request.head.sha || github.sha }} - D2B_MERGE_SHA: ${{ github.event.pull_request.merge_commit_sha || github.sha }} D2B_PR_NUMBER: ${{ github.event.pull_request.number || '' }} D2B_BRANCH: ${{ github.head_ref || github.ref_name }} D2B_RUN_ID: ${{ github.run_id }} @@ -112,8 +111,12 @@ jobs: fail "pull request number is invalid" is_sha "$D2B_BASE_SHA" || fail "pull request base SHA is invalid" is_sha "$D2B_HEAD_SHA" || fail "pull request head SHA is invalid" - is_sha "$D2B_MERGE_SHA" || fail "pull request merge SHA is invalid" - tested_sha="$D2B_MERGE_SHA" + tested_sha="$(git -C source rev-parse HEAD)" + is_sha "$tested_sha" || fail "live pull request merge ref did not resolve to a commit" + [ "$(git -C source show -s --format='%P' "$tested_sha")" = + "$D2B_BASE_SHA $D2B_HEAD_SHA" ] || + fail "live pull request merge ref does not bind the current base and head" + D2B_MERGE_SHA="$tested_sha" instance="d2b/pr/$D2B_PR_NUMBER/$D2B_HEAD_SHA/linux-x86_64/rules_rs/worker-v1/minimal/lock-v1" elif [ "$D2B_EVENT_NAME" = "push" ]; then [ "$D2B_CALLER_WORKFLOW_REF" = "vicondoa/d2b/.github/workflows/build.yaml@refs/heads/main" ] || @@ -123,10 +126,15 @@ jobs: fail "trusted build pushes are restricted to main" [ -z "$D2B_BASE_REF" ] || fail "push events must not carry a base ref" [ -z "$D2B_PR_NUMBER" ] || fail "push events must not carry PR metadata" - is_sha "$D2B_MERGE_SHA" || fail "pushed SHA is invalid" - D2B_BASE_SHA="$D2B_MERGE_SHA" - D2B_HEAD_SHA="$D2B_MERGE_SHA" - tested_sha="$D2B_MERGE_SHA" + is_sha "$D2B_CALLER_WORKFLOW_SHA" || + fail "pushed workflow SHA is invalid" + tested_sha="$(git -C source rev-parse HEAD)" + is_sha "$tested_sha" || fail "pushed revision is invalid" + [ "$tested_sha" = "$D2B_HEAD_SHA" ] || + fail "tested checkout does not match the pushed revision" + D2B_BASE_SHA="$tested_sha" + D2B_HEAD_SHA="$tested_sha" + D2B_MERGE_SHA="$tested_sha" instance="d2b/trusted-seed/main/$tested_sha/linux-x86_64/rules_rs/worker-v1/minimal/lock-v1" else fail "only pull_request_target and push events are accepted" diff --git a/CHANGELOG.md b/CHANGELOG.md index d25fb838d0..adc8b6c5a0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -55,6 +55,9 @@ deprecations ship one minor release before removal. - Serialized the main push workflow's nested local Layer-1 fan-out on hosted runners so concurrent Nix evaluation and Rust compilation cannot exhaust the runner before the aggregate check reports. +- Resolved pull-request tests from the live synthetic merge ref and verified + its immutable base and head parents instead of trusting a stale event merge + SHA. ## [1.4.1] - 2026-07-12 diff --git a/packages/xtask/tests/policy_ci.rs b/packages/xtask/tests/policy_ci.rs index dd2544cac8..3cd1319792 100644 --- a/packages/xtask/tests/policy_ci.rs +++ b/packages/xtask/tests/policy_ci.rs @@ -292,10 +292,14 @@ fn main_controlled_buildbuddy_workflows_preserve_trust_contract() { "credential-bearing remote execution must be restricted to trusted main pushes" ); assert!( - build.contains("refs/heads/$D2B_DEFAULT_BRANCH") + build.contains("format('refs/pull/{0}/merge', github.event.pull_request.number)") + && build.contains("git -C source show -s --format='%P' \"$tested_sha\"") + && build.contains("live pull request merge ref does not bind the current base and head") + && !build.contains("github.event.pull_request.merge_commit_sha") + && build.contains("refs/heads/$D2B_DEFAULT_BRANCH") && build.contains("secret = os.read(9, 4096)") && build.contains("9<&0"), - "PR ref validation and credential bootstrap must match GitHub's default-branch semantics" + "PR merge-ref validation and credential bootstrap must match GitHub's default-branch semantics" ); assert!( build.contains("redact") && build.contains("^warning:"), From 77ff613a19e379da5509b69c8ce4c45c7ddedf33 Mon Sep 17 00:00:00 2001 From: John Vicondoa Date: Sat, 22 Aug 2026 18:06:00 -0700 Subject: [PATCH 11/13] fix(ci): bind pull request merge parents live Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .github/workflows/build.yaml | 20 ++++++++++++++++++-- packages/xtask/tests/policy_ci.rs | 7 +++++++ 2 files changed, 25 insertions(+), 2 deletions(-) diff --git a/.github/workflows/build.yaml b/.github/workflows/build.yaml index 30fc1a4898..226f0efba0 100644 --- a/.github/workflows/build.yaml +++ b/.github/workflows/build.yaml @@ -111,10 +111,26 @@ jobs: fail "pull request number is invalid" is_sha "$D2B_BASE_SHA" || fail "pull request base SHA is invalid" is_sha "$D2B_HEAD_SHA" || fail "pull request head SHA is invalid" + resolve_remote_sha() { + local ref="$1" line remote_sha remote_ref extra + line="$(git -C source ls-remote --exit-code origin "$ref")" || + fail "live pull request ref is unavailable: $ref" + read -r remote_sha remote_ref extra <<<"$line" + is_sha "$remote_sha" && [ "$remote_ref" = "$ref" ] && [ -z "$extra" ] || + fail "live pull request ref is malformed: $ref" + printf '%s\n' "$remote_sha" + } tested_sha="$(git -C source rev-parse HEAD)" is_sha "$tested_sha" || fail "live pull request merge ref did not resolve to a commit" - [ "$(git -C source show -s --format='%P' "$tested_sha")" = - "$D2B_BASE_SHA $D2B_HEAD_SHA" ] || + read -r merge_parent_base merge_parent_head merge_parent_extra <<< \ + "$(git -C source show -s --format='%P' "$tested_sha")" + is_sha "$merge_parent_base" && is_sha "$merge_parent_head" && + [ -z "$merge_parent_extra" ] || + fail "live pull request merge ref must have exactly two parents" + D2B_BASE_SHA="$(resolve_remote_sha "refs/heads/$D2B_BASE_REF")" + D2B_HEAD_SHA="$(resolve_remote_sha "refs/pull/$D2B_PR_NUMBER/head")" + [ "$merge_parent_base" = "$D2B_BASE_SHA" ] && + [ "$merge_parent_head" = "$D2B_HEAD_SHA" ] || fail "live pull request merge ref does not bind the current base and head" D2B_MERGE_SHA="$tested_sha" instance="d2b/pr/$D2B_PR_NUMBER/$D2B_HEAD_SHA/linux-x86_64/rules_rs/worker-v1/minimal/lock-v1" diff --git a/packages/xtask/tests/policy_ci.rs b/packages/xtask/tests/policy_ci.rs index 3cd1319792..5bdff2ea2f 100644 --- a/packages/xtask/tests/policy_ci.rs +++ b/packages/xtask/tests/policy_ci.rs @@ -294,6 +294,13 @@ fn main_controlled_buildbuddy_workflows_preserve_trust_contract() { assert!( build.contains("format('refs/pull/{0}/merge', github.event.pull_request.number)") && build.contains("git -C source show -s --format='%P' \"$tested_sha\"") + && build.contains("git -C source ls-remote --exit-code origin \"$ref\"") + && build.contains("refs/heads/$D2B_BASE_REF") + && build.contains("refs/pull/$D2B_PR_NUMBER/head") + && build.contains("D2B_BASE_SHA=\"$(resolve_remote_sha") + && build.contains("D2B_HEAD_SHA=\"$(resolve_remote_sha") + && build.contains("[ \"$merge_parent_base\" = \"$D2B_BASE_SHA\" ]") + && build.contains("[ \"$merge_parent_head\" = \"$D2B_HEAD_SHA\" ]") && build.contains("live pull request merge ref does not bind the current base and head") && !build.contains("github.event.pull_request.merge_commit_sha") && build.contains("refs/heads/$D2B_DEFAULT_BRANCH") From f08900c1023d0392610510cc129fdb96f2194c8d Mon Sep 17 00:00:00 2001 From: John Vicondoa Date: Sat, 22 Aug 2026 18:19:07 -0700 Subject: [PATCH 12/13] fix(ci): pin pull request metadata snapshot Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .github/workflows/build.yaml | 82 ++++++++++++++++++++++++++----- packages/xtask/tests/policy_ci.rs | 13 +++-- 2 files changed, 79 insertions(+), 16 deletions(-) diff --git a/.github/workflows/build.yaml b/.github/workflows/build.yaml index 226f0efba0..c543710aa7 100644 --- a/.github/workflows/build.yaml +++ b/.github/workflows/build.yaml @@ -35,10 +35,64 @@ jobs: instance: ${{ steps.validate.outputs.instance }} has_bazel: ${{ steps.validate.outputs.has_bazel }} steps: + - id: resolve + name: Resolve live pull request merge ref + if: ${{ github.event_name == 'pull_request_target' }} + env: + D2B_PR_NUMBER: ${{ github.event.pull_request.number || '' }} + D2B_BASE_REF: ${{ github.base_ref }} + D2B_REPOSITORY: ${{ github.repository }} + run: | + set -euo pipefail + + fail() { + echo "::error::$1" >&2 + exit 76 + } + + is_sha() { + [[ "$1" =~ ^[0-9a-f]{40}$ ]] + } + + [ "$D2B_REPOSITORY" = "vicondoa/d2b" ] || + fail "the trusted build is restricted to vicondoa/d2b" + case "$D2B_BASE_REF" in + main|v3) ;; + *) fail "pull request target is not main or v3" ;; + esac + [ -n "$D2B_PR_NUMBER" ] && [[ "$D2B_PR_NUMBER" =~ ^[0-9]+$ ]] || + fail "pull request number is invalid" + + advertised_refs="$( + git ls-remote --exit-code "https://github.com/$D2B_REPOSITORY.git" \ + "refs/heads/$D2B_BASE_REF" \ + "refs/pull/$D2B_PR_NUMBER/head" \ + "refs/pull/$D2B_PR_NUMBER/merge" + )" || fail "live pull request refs are unavailable" + + advertised_sha() { + local ref="$1" line remote_sha remote_ref extra result="" count=0 + while IFS= read -r line; do + read -r remote_sha remote_ref extra <<<"$line" + if [ "$remote_ref" = "$ref" ]; then + count=$((count + 1)) + is_sha "$remote_sha" && [ -z "$extra" ] || + fail "live pull request ref is malformed: $ref" + result="$remote_sha" + fi + done <<<"$advertised_refs" + [ "$count" -eq 1 ] || + fail "live pull request ref is unavailable: $ref" + printf '%s\n' "$result" + } + + printf 'base_sha=%s\n' "$(advertised_sha "refs/heads/$D2B_BASE_REF")" >>"$GITHUB_OUTPUT" + printf 'head_sha=%s\n' "$(advertised_sha "refs/pull/$D2B_PR_NUMBER/head")" >>"$GITHUB_OUTPUT" + printf 'merge_sha=%s\n' "$(advertised_sha "refs/pull/$D2B_PR_NUMBER/merge")" >>"$GITHUB_OUTPUT" - name: Checkout immutable tested revision uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 with: - ref: ${{ github.event_name == 'push' && github.sha || format('refs/pull/{0}/merge', github.event.pull_request.number) }} + ref: ${{ github.event_name == 'push' && github.sha || steps.resolve.outputs.merge_sha }} path: source fetch-depth: 0 persist-credentials: false @@ -51,6 +105,9 @@ jobs: D2B_DEFAULT_BRANCH: ${{ github.event.repository.default_branch }} D2B_BASE_SHA: ${{ github.event.pull_request.base.sha || github.sha }} D2B_HEAD_SHA: ${{ github.event.pull_request.head.sha || github.sha }} + D2B_RESOLVED_BASE_SHA: ${{ steps.resolve.outputs.base_sha || '' }} + D2B_RESOLVED_HEAD_SHA: ${{ steps.resolve.outputs.head_sha || '' }} + D2B_RESOLVED_MERGE_SHA: ${{ steps.resolve.outputs.merge_sha || '' }} D2B_PR_NUMBER: ${{ github.event.pull_request.number || '' }} D2B_BRANCH: ${{ github.head_ref || github.ref_name }} D2B_RUN_ID: ${{ github.run_id }} @@ -111,24 +168,25 @@ jobs: fail "pull request number is invalid" is_sha "$D2B_BASE_SHA" || fail "pull request base SHA is invalid" is_sha "$D2B_HEAD_SHA" || fail "pull request head SHA is invalid" - resolve_remote_sha() { - local ref="$1" line remote_sha remote_ref extra - line="$(git -C source ls-remote --exit-code origin "$ref")" || - fail "live pull request ref is unavailable: $ref" - read -r remote_sha remote_ref extra <<<"$line" - is_sha "$remote_sha" && [ "$remote_ref" = "$ref" ] && [ -z "$extra" ] || - fail "live pull request ref is malformed: $ref" - printf '%s\n' "$remote_sha" - } tested_sha="$(git -C source rev-parse HEAD)" is_sha "$tested_sha" || fail "live pull request merge ref did not resolve to a commit" + is_sha "$D2B_RESOLVED_BASE_SHA" || + fail "resolved pull request base SHA is invalid" + is_sha "$D2B_RESOLVED_HEAD_SHA" || + fail "resolved pull request head SHA is invalid" + is_sha "$D2B_RESOLVED_MERGE_SHA" || + fail "resolved pull request merge SHA is invalid" + [ "$tested_sha" = "$D2B_RESOLVED_MERGE_SHA" ] || + fail "tested checkout does not match the resolved pull request merge ref" + [ "$D2B_RESOLVED_HEAD_SHA" = "$D2B_HEAD_SHA" ] || + fail "pull request head changed during workflow dispatch" read -r merge_parent_base merge_parent_head merge_parent_extra <<< \ "$(git -C source show -s --format='%P' "$tested_sha")" is_sha "$merge_parent_base" && is_sha "$merge_parent_head" && [ -z "$merge_parent_extra" ] || fail "live pull request merge ref must have exactly two parents" - D2B_BASE_SHA="$(resolve_remote_sha "refs/heads/$D2B_BASE_REF")" - D2B_HEAD_SHA="$(resolve_remote_sha "refs/pull/$D2B_PR_NUMBER/head")" + D2B_BASE_SHA="$D2B_RESOLVED_BASE_SHA" + D2B_HEAD_SHA="$D2B_RESOLVED_HEAD_SHA" [ "$merge_parent_base" = "$D2B_BASE_SHA" ] && [ "$merge_parent_head" = "$D2B_HEAD_SHA" ] || fail "live pull request merge ref does not bind the current base and head" diff --git a/packages/xtask/tests/policy_ci.rs b/packages/xtask/tests/policy_ci.rs index 5bdff2ea2f..db472948b6 100644 --- a/packages/xtask/tests/policy_ci.rs +++ b/packages/xtask/tests/policy_ci.rs @@ -292,15 +292,20 @@ fn main_controlled_buildbuddy_workflows_preserve_trust_contract() { "credential-bearing remote execution must be restricted to trusted main pushes" ); assert!( - build.contains("format('refs/pull/{0}/merge', github.event.pull_request.number)") + build.contains("name: Resolve live pull request merge ref") + && build.contains("git ls-remote --exit-code \"https://github.com/$D2B_REPOSITORY.git\"") && build.contains("git -C source show -s --format='%P' \"$tested_sha\"") - && build.contains("git -C source ls-remote --exit-code origin \"$ref\"") && build.contains("refs/heads/$D2B_BASE_REF") && build.contains("refs/pull/$D2B_PR_NUMBER/head") - && build.contains("D2B_BASE_SHA=\"$(resolve_remote_sha") - && build.contains("D2B_HEAD_SHA=\"$(resolve_remote_sha") + && build.contains("D2B_RESOLVED_BASE_SHA") + && build.contains("D2B_RESOLVED_HEAD_SHA") + && build.contains("D2B_RESOLVED_MERGE_SHA") + && build.contains("[ \"$tested_sha\" = \"$D2B_RESOLVED_MERGE_SHA\" ]") + && build.contains("[ \"$D2B_RESOLVED_HEAD_SHA\" = \"$D2B_HEAD_SHA\" ]") && build.contains("[ \"$merge_parent_base\" = \"$D2B_BASE_SHA\" ]") && build.contains("[ \"$merge_parent_head\" = \"$D2B_HEAD_SHA\" ]") + && build.contains("live pull request refs are unavailable") + && build.contains("live pull request ref is malformed") && build.contains("live pull request merge ref does not bind the current base and head") && !build.contains("github.event.pull_request.merge_commit_sha") && build.contains("refs/heads/$D2B_DEFAULT_BRANCH") From b7d19dc352ebdd04ded650caad24e25122cd9083 Mon Sep 17 00:00:00 2001 From: John Vicondoa Date: Sat, 22 Aug 2026 18:34:38 -0700 Subject: [PATCH 13/13] fix(ci): fail closed on missing pull refs Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .github/workflows/build.yaml | 9 ++++++--- packages/xtask/tests/policy_ci.rs | 6 ++++++ 2 files changed, 12 insertions(+), 3 deletions(-) diff --git a/.github/workflows/build.yaml b/.github/workflows/build.yaml index c543710aa7..486400a035 100644 --- a/.github/workflows/build.yaml +++ b/.github/workflows/build.yaml @@ -86,9 +86,12 @@ jobs: printf '%s\n' "$result" } - printf 'base_sha=%s\n' "$(advertised_sha "refs/heads/$D2B_BASE_REF")" >>"$GITHUB_OUTPUT" - printf 'head_sha=%s\n' "$(advertised_sha "refs/pull/$D2B_PR_NUMBER/head")" >>"$GITHUB_OUTPUT" - printf 'merge_sha=%s\n' "$(advertised_sha "refs/pull/$D2B_PR_NUMBER/merge")" >>"$GITHUB_OUTPUT" + base_sha="$(advertised_sha "refs/heads/$D2B_BASE_REF")" + head_sha="$(advertised_sha "refs/pull/$D2B_PR_NUMBER/head")" + merge_sha="$(advertised_sha "refs/pull/$D2B_PR_NUMBER/merge")" + printf 'base_sha=%s\n' "$base_sha" >>"$GITHUB_OUTPUT" + printf 'head_sha=%s\n' "$head_sha" >>"$GITHUB_OUTPUT" + printf 'merge_sha=%s\n' "$merge_sha" >>"$GITHUB_OUTPUT" - name: Checkout immutable tested revision uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 with: diff --git a/packages/xtask/tests/policy_ci.rs b/packages/xtask/tests/policy_ci.rs index db472948b6..c6983b8d6c 100644 --- a/packages/xtask/tests/policy_ci.rs +++ b/packages/xtask/tests/policy_ci.rs @@ -293,6 +293,8 @@ fn main_controlled_buildbuddy_workflows_preserve_trust_contract() { ); assert!( build.contains("name: Resolve live pull request merge ref") + && build.contains("if: ${{ github.event_name == 'pull_request_target' }}") + && build.contains("ref: ${{ github.event_name == 'push' && github.sha || steps.resolve.outputs.merge_sha }}") && build.contains("git ls-remote --exit-code \"https://github.com/$D2B_REPOSITORY.git\"") && build.contains("git -C source show -s --format='%P' \"$tested_sha\"") && build.contains("refs/heads/$D2B_BASE_REF") @@ -300,6 +302,10 @@ fn main_controlled_buildbuddy_workflows_preserve_trust_contract() { && build.contains("D2B_RESOLVED_BASE_SHA") && build.contains("D2B_RESOLVED_HEAD_SHA") && build.contains("D2B_RESOLVED_MERGE_SHA") + && build.contains("base_sha=\"$(advertised_sha") + && build.contains("head_sha=\"$(advertised_sha") + && build.contains("merge_sha=\"$(advertised_sha") + && !build.contains("printf 'base_sha=%s\\n' \"$(advertised_sha") && build.contains("[ \"$tested_sha\" = \"$D2B_RESOLVED_MERGE_SHA\" ]") && build.contains("[ \"$D2B_RESOLVED_HEAD_SHA\" = \"$D2B_HEAD_SHA\" ]") && build.contains("[ \"$merge_parent_base\" = \"$D2B_BASE_SHA\" ]")