diff --git a/.github/workflows/fleet-converge.yml b/.github/workflows/fleet-converge.yml index 891570deec9..efe10c72cc6 100644 --- a/.github/workflows/fleet-converge.yml +++ b/.github/workflows/fleet-converge.yml @@ -382,6 +382,7 @@ jobs: 'printf' '%s' 'claim_executor gunbc build_diagnostics + claim_executor_checker_inputs ' > "$RUNNER_TEMP"'/release-bins.roster' 'tar' '-tzf' "$STAGE"'/release-bins.tgz' > "$RUNNER_TEMP"'/pack-listing.out' 'cmp' '-s' "$RUNNER_TEMP"'/pack-listing.out' "$RUNNER_TEMP"'/release-bins.roster' || ('echo' '::error title=release-bins PackListingNotRoster::the stored pack'\''s archive listing is not exactly the roster (an extra, duplicate, directory or escaping entry)'; exit 1) @@ -402,6 +403,7 @@ jobs: 'mkdir' '-p' "$ROOT"'/target/release' ('cd' "$ROOT"'/target/release' && 'rm' '-f' 'claim_executor' 'gunbc') ('cd' "$STAGE"'/unpacked' && 'cp' '-p' 'claim_executor' 'gunbc' "$ROOT"'/target/release/') + ('cd' "$STAGE"'/unpacked' && 'cp' '-p' 'claim_executor_checker_inputs' "$ROOT"'/target/release/') 'rm' '-rf' "$STAGE"'/unpacked' 'echo' 'release-bins: stored pack verified and promoted' 'sha256sum' "$STAGE"'/release-bins.manifest' > "$RUNNER_TEMP"'/OUTPUT_DIGEST.out' @@ -544,8 +546,10 @@ jobs: 'mkdir' '-p' "$STAGE" '[' '-s' "$ROOT"'/gunbc-floor-cmd.log' ']' || ('echo' '::error title=release-bins BuildDiagnosticsMissing::the product requires the build log gunbc-floor-cmd.log as a pack member and the build step left none; a pack without its diagnostics is incomplete, not smaller'; exit 1) 'cp' "$ROOT"'/gunbc-floor-cmd.log' "$ROOT"'/target/release/build_diagnostics' - ('cd' "$ROOT"'/target/release' && 'sha256sum' 'claim_executor' 'gunbc' 'build_diagnostics') > "$STAGE"'/release-bins.manifest' - 'tar' '-czf' "$STAGE"'/release-bins.tgz' '-C' "$ROOT"'/target/release' 'claim_executor' 'gunbc' 'build_diagnostics' + './target/release/claim_executor' '--write-checker-inputs' "$ROOT"'/target/release/claim_executor_checker_inputs' + '[' '-s' "$ROOT"'/target/release/claim_executor_checker_inputs' ']' || ('echo' '::error title=release-bins SidecarMissing::the product requires the sidecar claim_executor_checker_inputs as a pack member and the producing job left none; a pack without it is incomplete, not smaller'; exit 1) + ('cd' "$ROOT"'/target/release' && 'sha256sum' 'claim_executor' 'gunbc' 'build_diagnostics' 'claim_executor_checker_inputs') > "$STAGE"'/release-bins.manifest' + 'tar' '-czf' "$STAGE"'/release-bins.tgz' '-C' "$ROOT"'/target/release' 'claim_executor' 'gunbc' 'build_diagnostics' 'claim_executor_checker_inputs' 'cp' "$RUNNER_TEMP"'/release-bins.preimage.expected' "$STAGE"'/release-bins.preimage' 'sha256sum' "$STAGE"'/release-bins.manifest' > "$RUNNER_TEMP"'/OUTPUT_DIGEST.out' OUTPUT_DIGEST=$('cut' '-d' ' ' '-f' '1' "$RUNNER_TEMP"'/OUTPUT_DIGEST.out') @@ -627,6 +631,7 @@ jobs: 'printf' '%s' 'claim_executor gunbc build_diagnostics + claim_executor_checker_inputs ' > "$RUNNER_TEMP"'/release-bins.roster' 'tar' '-tzf' "$RUNNER_TEMP"'/release-bins-download/release-bins.tgz' > "$RUNNER_TEMP"'/pack-listing.out' 'cmp' '-s' "$RUNNER_TEMP"'/pack-listing.out' "$RUNNER_TEMP"'/release-bins.roster' || ('echo' '::error title=release-bins PackListingNotRoster::the downloaded pack'\''s archive listing is not exactly the roster (an extra, duplicate, directory or escaping entry)'; exit 1) @@ -647,6 +652,7 @@ jobs: 'mkdir' '-p' "$ROOT"'/target/release' ('cd' "$ROOT"'/target/release' && 'rm' '-f' 'claim_executor' 'gunbc') ('cd' "$RUNNER_TEMP"'/release-bins-check' && 'cp' '-p' 'claim_executor' 'gunbc' "$ROOT"'/target/release/') + ('cd' "$RUNNER_TEMP"'/release-bins-check' && 'cp' '-p' 'claim_executor_checker_inputs' "$ROOT"'/target/release/') 'rm' '-rf' "$RUNNER_TEMP"'/release-bins-check' "$ROOT"'/target/release/claim_executor' '--verify-build-artifacts' "$ROOT"'/target/release/claim_executor' "$ROOT"'/target/release/gunbc' diff --git a/.github/workflows/fleet-desired.yml b/.github/workflows/fleet-desired.yml index 12ee3f523b1..f9ff7b0c00e 100644 --- a/.github/workflows/fleet-desired.yml +++ b/.github/workflows/fleet-desired.yml @@ -87,6 +87,7 @@ jobs: 'printf' '%s' 'claim_executor gunbc build_diagnostics + claim_executor_checker_inputs ' > "$RUNNER_TEMP"'/release-bins.roster' 'tar' '-tzf' "$RUNNER_TEMP"'/canonical-pack/release-bins.tgz' > "$RUNNER_TEMP"'/pack-listing.out' 'cmp' '-s' "$RUNNER_TEMP"'/pack-listing.out' "$RUNNER_TEMP"'/release-bins.roster' || ('echo' '::error title=release-bins PackListingNotRoster::the downloaded pack'\''s archive listing is not exactly the roster (an extra, duplicate, directory or escaping entry)'; exit 1) @@ -107,6 +108,7 @@ jobs: 'mkdir' '-p' "$ROOT"'/target/release' ('cd' "$ROOT"'/target/release' && 'rm' '-f' 'claim_executor' 'gunbc') ('cd' "$RUNNER_TEMP"'/canonical-check' && 'cp' '-p' 'claim_executor' 'gunbc' "$ROOT"'/target/release/') + ('cd' "$RUNNER_TEMP"'/canonical-check' && 'cp' '-p' 'claim_executor_checker_inputs' "$ROOT"'/target/release/') 'rm' '-rf' "$RUNNER_TEMP"'/canonical-check' "$ROOT"'/target/release/claim_executor' '--verify-build-artifacts' "$ROOT"'/target/release/claim_executor' "$ROOT"'/target/release/gunbc' 'echo' 'release-bins: canonical gunbc.compiler_pair_release_bins pack verified and promoted' diff --git a/.github/workflows/witnesses.yml b/.github/workflows/witnesses.yml index 9a01783e06a..0c6eeffc5e6 100644 --- a/.github/workflows/witnesses.yml +++ b/.github/workflows/witnesses.yml @@ -77,6 +77,304 @@ jobs: 'exit' "$GUNBC_FLOOR_EXIT" timeout-minutes: 15 + seed: + runs-on: [self-hosted, linux, arm64] + timeout-minutes: 90 + if: github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == github.repository + permissions: + contents: read + actions: read + outputs: + release_bins_key: ${{ steps.release_bins_key.outputs.key }} + steps: + - name: Checkout the commit this lane judges + uses: actions/checkout@fbc6f3992d24b796d5a048ff273f7fcc4a7b6c09 + with: + fetch-depth: 0 + ref: ${{ github.event.pull_request.head.sha }} + persist-credentials: false + - name: Isolate toolchain homes + run: |- + # dissolve-on: ci_toolchain_home_isolation_script -- orch-emitted foreign-executor prelude step wiping and setting HOME/CARGO_HOME/RUSTUP_HOME under RUNNER_TEMP so concurrent runner slots stop sharing one toolchain; leaf rm/echo strings remain until a typed per-job filesystem-and-environment effect lands on host_effect_apply (shell-to-intent Phase 2). This obligation covers THIS carrier and ci_isolate_toolchain_script, which share that terminal construction; ci_pin_rustup_default_script carries its own obligation because it does not + rm -rf "$RUNNER_TEMP/rustup" "$RUNNER_TEMP/cargo" + echo "HOME=$RUNNER_TEMP" >> "$GITHUB_ENV" + echo "CARGO_HOME=$RUNNER_TEMP/cargo" >> "$GITHUB_ENV" + echo "RUSTUP_HOME=$RUNNER_TEMP/rustup" >> "$GITHUB_ENV" + - name: Install Rust toolchain + uses: actions-rust-lang/setup-rust-toolchain@2b1f5e9b395427c92ee4e3331786ca3c37afe2d7 + with: + components: rustfmt + cache: false + rustflags: -D warnings + - name: Pin rustup default (isolated RUSTUP_HOME has no default toolchain) + run: |- + # dissolve-on: ci_pin_rustup_default_script -- orch-emitted foreign-executor step selecting a rustup default toolchain inside an isolated RUSTUP_HOME, which starts with none, and resolving the cargo binary that selection implies. The leaf rustup/command/echo strings remain until a typed TOOLCHAIN-SELECTION effect lands on host_effect_apply -- NOT the filesystem-and-environment effect ci_toolchain_home_isolation_script waits on, which is why this is a separate obligation: that effect landing alone would leave this carrier standing + rustup default "$(rustup show active-toolchain | awk '{print $1; exit}')" + if [ -x "$CARGO_HOME/bin/cargo" ]; then CARGO_BIN="$CARGO_HOME/bin/cargo"; else CARGO_BIN="$(command -v cargo || true)"; fi + if [ -z "$CARGO_BIN" ]; then echo "::error::no cargo binary: neither the isolated $CARGO_HOME/bin/cargo shim nor PATH carries one"; exit 1; fi + echo "CARGO_BIN=$CARGO_BIN" >> "$GITHUB_ENV" + - name: Key the compiler pair (stamps the build with the materialized tree) + id: release_bins_key + run: | + set -e + ROOT=$('git' 'rev-parse' '--show-toplevel') + DIRTY=$('git' '-C' "$ROOT" 'status' '--porcelain' '--untracked-files=no') + '[' '-z' "$DIRTY" ']' || ('echo' '::error title=release-bins SourceTreeDirty::tracked files differ from HEAD, so HEAD^{tree} does not name the build input; refusing to key'; exit 1) + 'env' '-0' > "$RUNNER_TEMP"'/env.nul' + if 'grep' '-zqE' '^(CARGO|RUST|CC|CXX|AR|CFLAGS|CXXFLAGS|LDFLAGS|TARGET_CC|HOST_CC)[A-Za-z0-9_]*(TOKEN|CREDENTIAL|SECRET|PASSWORD|PASSWD|AUTH)[A-Za-z0-9_]*=' "$RUNNER_TEMP"'/env.nul'; then ('echo' '::error title=release-bins CredentialInBuildEnvironment::a registry or toolchain credential is present in the build environment; credentials are not build inputs and must not reach a key or a published preimage, so the build is refused'; exit 1); fi + if 'grep' '-zqE' '^(RUSTC|RUSTC_WORKSPACE_WRAPPER|CARGO_BUILD_RUSTC|CARGO_BUILD_RUSTC_WRAPPER|CARGO_BUILD_RUSTC_WORKSPACE_WRAPPER|CARGO_BUILD_TARGET|CARGO_BUILD_TARGET_DIR|CARGO_TARGET_DIR|CARGO_TARGET_[A-Z0-9_]*_(LINKER|RUNNER))=' "$RUNNER_TEMP"'/env.nul'; then ('echo' '::error title=release-bins BuildOverrideNotAdmitted::the environment overrides the compiler, linker, target or target directory Cargo would use; the key probes only the admitted defaults, so the build is refused rather than keyed wrongly'; exit 1); fi + 'sed' '-z' '-nE' '/^(CARGO_[A-Z0-9_]*|RUST[A-Z0-9_]*|CC|CXX|AR|CFLAGS|CXXFLAGS|LDFLAGS|CC_[A-Za-z0-9_]*|CFLAGS_[A-Za-z0-9_]*|TARGET_CC|HOST_CC)=/p' "$RUNNER_TEMP"'/env.nul' > "$RUNNER_TEMP"'/env-keyed.nul' + 'sed' '-z' '-E' '/^(CARGO_HOME|RUSTUP_HOME|CARGO_BIN|CARGO_TERM_COLOR|CARGO_BUILD_JOBS|RUSTC_WRAPPER)=/d' "$RUNNER_TEMP"'/env-keyed.nul' > "$RUNNER_TEMP"'/env-kept.nul' + 'sort' '-z' "$RUNNER_TEMP"'/env-kept.nul' > "$RUNNER_TEMP"'/env-sorted.nul' + 'sha256sum' "$RUNNER_TEMP"'/env-sorted.nul' > "$RUNNER_TEMP"'/AXIS_ENVIRONMENT_DIGEST.out' + AXIS_ENVIRONMENT_DIGEST=$('cut' '-d' ' ' '-f' '1' "$RUNNER_TEMP"'/AXIS_ENVIRONMENT_DIGEST.out') + ANCESTOR_0="$ROOT" + ANCESTOR_CONFIG='' + ANCESTOR_1=$('dirname' "$ANCESTOR_0") + ('[' '!' '-e' "$ANCESTOR_1"'/.cargo/config.toml' ']' && '[' '!' '-e' "$ANCESTOR_1"'/.cargo/config' ']') || ANCESTOR_CONFIG="$ANCESTOR_1" + ANCESTOR_2=$('dirname' "$ANCESTOR_1") + ('[' '!' '-e' "$ANCESTOR_2"'/.cargo/config.toml' ']' && '[' '!' '-e' "$ANCESTOR_2"'/.cargo/config' ']') || ANCESTOR_CONFIG="$ANCESTOR_2" + ANCESTOR_3=$('dirname' "$ANCESTOR_2") + ('[' '!' '-e' "$ANCESTOR_3"'/.cargo/config.toml' ']' && '[' '!' '-e' "$ANCESTOR_3"'/.cargo/config' ']') || ANCESTOR_CONFIG="$ANCESTOR_3" + ANCESTOR_4=$('dirname' "$ANCESTOR_3") + ('[' '!' '-e' "$ANCESTOR_4"'/.cargo/config.toml' ']' && '[' '!' '-e' "$ANCESTOR_4"'/.cargo/config' ']') || ANCESTOR_CONFIG="$ANCESTOR_4" + ANCESTOR_5=$('dirname' "$ANCESTOR_4") + ('[' '!' '-e' "$ANCESTOR_5"'/.cargo/config.toml' ']' && '[' '!' '-e' "$ANCESTOR_5"'/.cargo/config' ']') || ANCESTOR_CONFIG="$ANCESTOR_5" + ANCESTOR_6=$('dirname' "$ANCESTOR_5") + ('[' '!' '-e' "$ANCESTOR_6"'/.cargo/config.toml' ']' && '[' '!' '-e' "$ANCESTOR_6"'/.cargo/config' ']') || ANCESTOR_CONFIG="$ANCESTOR_6" + ANCESTOR_7=$('dirname' "$ANCESTOR_6") + ('[' '!' '-e' "$ANCESTOR_7"'/.cargo/config.toml' ']' && '[' '!' '-e' "$ANCESTOR_7"'/.cargo/config' ']') || ANCESTOR_CONFIG="$ANCESTOR_7" + ANCESTOR_8=$('dirname' "$ANCESTOR_7") + ('[' '!' '-e' "$ANCESTOR_8"'/.cargo/config.toml' ']' && '[' '!' '-e' "$ANCESTOR_8"'/.cargo/config' ']') || ANCESTOR_CONFIG="$ANCESTOR_8" + ANCESTOR_9=$('dirname' "$ANCESTOR_8") + ('[' '!' '-e' "$ANCESTOR_9"'/.cargo/config.toml' ']' && '[' '!' '-e' "$ANCESTOR_9"'/.cargo/config' ']') || ANCESTOR_CONFIG="$ANCESTOR_9" + ANCESTOR_10=$('dirname' "$ANCESTOR_9") + ('[' '!' '-e' "$ANCESTOR_10"'/.cargo/config.toml' ']' && '[' '!' '-e' "$ANCESTOR_10"'/.cargo/config' ']') || ANCESTOR_CONFIG="$ANCESTOR_10" + ANCESTOR_11=$('dirname' "$ANCESTOR_10") + ('[' '!' '-e' "$ANCESTOR_11"'/.cargo/config.toml' ']' && '[' '!' '-e' "$ANCESTOR_11"'/.cargo/config' ']') || ANCESTOR_CONFIG="$ANCESTOR_11" + ANCESTOR_12=$('dirname' "$ANCESTOR_11") + ('[' '!' '-e' "$ANCESTOR_12"'/.cargo/config.toml' ']' && '[' '!' '-e' "$ANCESTOR_12"'/.cargo/config' ']') || ANCESTOR_CONFIG="$ANCESTOR_12" + ANCESTOR_13=$('dirname' "$ANCESTOR_12") + ('[' '!' '-e' "$ANCESTOR_13"'/.cargo/config.toml' ']' && '[' '!' '-e' "$ANCESTOR_13"'/.cargo/config' ']') || ANCESTOR_CONFIG="$ANCESTOR_13" + ANCESTOR_14=$('dirname' "$ANCESTOR_13") + ('[' '!' '-e' "$ANCESTOR_14"'/.cargo/config.toml' ']' && '[' '!' '-e' "$ANCESTOR_14"'/.cargo/config' ']') || ANCESTOR_CONFIG="$ANCESTOR_14" + ANCESTOR_15=$('dirname' "$ANCESTOR_14") + ('[' '!' '-e' "$ANCESTOR_15"'/.cargo/config.toml' ']' && '[' '!' '-e' "$ANCESTOR_15"'/.cargo/config' ']') || ANCESTOR_CONFIG="$ANCESTOR_15" + ANCESTOR_16=$('dirname' "$ANCESTOR_15") + ('[' '!' '-e' "$ANCESTOR_16"'/.cargo/config.toml' ']' && '[' '!' '-e' "$ANCESTOR_16"'/.cargo/config' ']') || ANCESTOR_CONFIG="$ANCESTOR_16" + if '[' '-n' "$ANCESTOR_CONFIG" ']'; then 'echo' 'release-bins: Cargo config found above the checkout in '"$ANCESTOR_CONFIG"; ('echo' '::error title=release-bins AncestorCargoConfigPresent::a Cargo config file exists in a directory above the checkout; it is outside the committed tree the key covers, so the build is refused'; exit 1); fi + '[' "$ANCESTOR_16" '=' '/' ']' || ('echo' '::error title=release-bins CheckoutPathTooDeep::the checkout is deeper than the ancestor Cargo-config scan covers, so ancestor configuration cannot be ruled out'; exit 1) + OBJECT_FORMAT=$('git' '-C' "$ROOT" 'rev-parse' '--show-object-format') + SOURCE_TREE=$('git' '-C' "$ROOT" 'rev-parse' 'HEAD^{tree}') + '[' '-n' "$OBJECT_FORMAT" ']' || ('echo' '::error title=release-bins AxisValueUnread::the key input OBJECT_FORMAT read back empty, so the key would not name the build; refusing to key'; exit 1) + '[' '-n' "$SOURCE_TREE" ']' || ('echo' '::error title=release-bins AxisValueUnread::the key input SOURCE_TREE read back empty, so the key would not name the build; refusing to key'; exit 1) + SOURCE_CLOSURE='tree:'"$OBJECT_FORMAT"':'"$SOURCE_TREE" + EXPECTED="$RUNNER_TEMP"'/release-bins.preimage.expected' + AXIS_PRODUCER=$("$CARGO_BIN" '-V') + '[' '-n' "$AXIS_PRODUCER" ']' || ('echo' '::error title=release-bins AxisValueUnread::the key input AXIS_PRODUCER read back empty, so the key would not name the build; refusing to key'; exit 1) + 'rustc' '-vV' > "$RUNNER_TEMP"'/rustc-host.out' + AXIS_HOST=$('sed' '-n' 's/^host: //p' "$RUNNER_TEMP"'/rustc-host.out') + 'cc' '--version' > "$RUNNER_TEMP"'/AXIS_CC.out' + AXIS_CC=$('head' '-n' '1' "$RUNNER_TEMP"'/AXIS_CC.out') + 'ld' '--version' > "$RUNNER_TEMP"'/AXIS_LD.out' + AXIS_LD=$('head' '-n' '1' "$RUNNER_TEMP"'/AXIS_LD.out') + 'ldd' '--version' > "$RUNNER_TEMP"'/AXIS_LIBC.out' + AXIS_LIBC=$('head' '-n' '1' "$RUNNER_TEMP"'/AXIS_LIBC.out') + '[' '-n' "$AXIS_HOST" ']' || ('echo' '::error title=release-bins AxisValueUnread::the key input AXIS_HOST read back empty, so the key would not name the build; refusing to key'; exit 1) + '[' '-n' "$AXIS_CC" ']' || ('echo' '::error title=release-bins AxisValueUnread::the key input AXIS_CC read back empty, so the key would not name the build; refusing to key'; exit 1) + '[' '-n' "$AXIS_LD" ']' || ('echo' '::error title=release-bins AxisValueUnread::the key input AXIS_LD read back empty, so the key would not name the build; refusing to key'; exit 1) + '[' '-n' "$AXIS_LIBC" ']' || ('echo' '::error title=release-bins AxisValueUnread::the key input AXIS_LIBC read back empty, so the key would not name the build; refusing to key'; exit 1) + 'rustc' '-vV' > "$RUNNER_TEMP"'/rustc-vV.out' + AXIS_TOOLCHAIN=$('paste' '-s' '-d' ';' "$RUNNER_TEMP"'/rustc-vV.out') + '[' '-n' "$AXIS_TOOLCHAIN" ']' || ('echo' '::error title=release-bins AxisValueUnread::the key input AXIS_TOOLCHAIN read back empty, so the key would not name the build; refusing to key'; exit 1) + ('printf' 'kind=%s\n' 'gunbc.compiler_pair_release_bins'; 'printf' 'address=sha256 scope=cross_run\n'; 'printf' 'producer_compiler=%s\n' "$AXIS_PRODUCER"; 'printf' 'source_closure=%s\n' "$SOURCE_CLOSURE"; 'printf' 'target_model=%s;cc=%s;ld=%s;libc=%s\n' "$AXIS_HOST" "$AXIS_CC" "$AXIS_LD" "$AXIS_LIBC"; 'printf' 'toolchain=%s\n' "$AXIS_TOOLCHAIN"; 'printf' 'build_configuration=%s;embedded_build_identity=%s;environment=%s;roster=%s\n' 'cargo build --release -p v1-compiler --bin claim_executor --bin gunbc' "$SOURCE_CLOSURE" 'sha256:'"$AXIS_ENVIRONMENT_DIGEST" 'claim_executor gunbc'; 'printf' 'required_lens_contract=none (a seed cargo build admits no lens contract)\n') > "$EXPECTED" + 'sha256sum' "$EXPECTED" > "$RUNNER_TEMP"'/PREIMAGE_DIGEST.out' + PREIMAGE_DIGEST=$('cut' '-d' ' ' '-f' '1' "$RUNNER_TEMP"'/PREIMAGE_DIGEST.out') + KEY='release-bins-'"$PREIMAGE_DIGEST" + 'rm' '-rf' "$ROOT"'/.release-bins-staging' + 'mkdir' '-p' "$ROOT"'/.release-bins-staging' + 'echo' 'release-bins request key '"$KEY"' over preimage:' + 'cat' "$EXPECTED" + 'echo' 'key='"$KEY" | 'tee' '-a' "$GITHUB_OUTPUT" > '/dev/null' + 'echo' 'GUNBC_MATERIALIZED_TREE_IDENTITY='"$SOURCE_CLOSURE" | 'tee' '-a' "$GITHUB_ENV" > '/dev/null' + timeout-minutes: 5 + - name: Look up release-bins by exact key under the admitted producer refs (cache API; a failed lookup is not absence) + id: release_bins_lookup + run: | + set -e + ROOT=$('git' 'rev-parse' '--show-toplevel') + STANDING='absent' + ENTRY='none' + 'curl' '-sSf' '-H' 'Authorization: Bearer '"$GH_TOKEN" '-H' 'Accept: application/vnd.github+json' "$GITHUB_API_URL"'/repos/'"$GITHUB_REPOSITORY"'/actions/caches?per_page=100&key='"$RELEASE_BINS_KEY"'&ref=''refs/heads/main' '-o' "$ROOT"'/.release-bins-lookup-main.json' || ('echo' '::error title=release-bins LookupTransportFailed::the cache API lookup FAILED (unreachable or refused); this is not absence, so neither reuse nor rebuild is decided'; exit 1) + 'jq' '--raw-output' '--exit-status' '. as $answer | [inputs] | if length != 0 then error("the cache-API answer is not exactly one JSON document") else $answer | if type == "object" and (.total_count | type) == "number" and (.actions_caches | type) == "array" and .total_count == (.actions_caches | length) and .total_count <= 100 and all(.actions_caches[]; type == "object" and (.key | type) == "string" and (.ref | type) == "string") then [.actions_caches[] | select(.key == $ENV.RELEASE_BINS_KEY and .ref == "refs/heads/main")] | if length == 0 then "absent" elif length == 1 then "present\nid=\(.[0].id) ref=\(.[0].ref) version=\(.[0].version) created=\(.[0].created_at)" else error("duplicate exact entries") end else error("cache-API answer is not the admitted shape") end end' '.release-bins-lookup-main.json' > "$RUNNER_TEMP"'/lookup-main.decoded' || ('echo' '::error title=release-bins LookupAnswerUnusable::the cache API answer was unreadable or incomplete; it is not treated as absence'; exit 1) + DECODED_STANDING=$('head' '-n' '1' "$RUNNER_TEMP"'/lookup-main.decoded') + if '[' "$DECODED_STANDING" '=' 'present' ']'; then STANDING='present'; ENTRY=$('sed' '-n' '2p' "$RUNNER_TEMP"'/lookup-main.decoded'); else '[' "$DECODED_STANDING" '=' 'absent' ']' || ('echo' '::error title=release-bins LookupAnswerUnusable::the cache API answer was unreadable or incomplete; it is not treated as absence'; exit 1); fi + 'curl' '-sSf' '-H' 'Authorization: Bearer '"$GH_TOKEN" '-H' 'Accept: application/vnd.github+json' "$GITHUB_API_URL"'/repos/'"$GITHUB_REPOSITORY"'/actions/caches?per_page=100&key='"$RELEASE_BINS_KEY"'&ref='"$RELEASE_BINS_DISPATCH_SCOPE_REF" '-o' "$ROOT"'/.release-bins-lookup-dispatch.json' || ('echo' '::error title=release-bins LookupTransportFailed::the cache API lookup FAILED (unreachable or refused); this is not absence, so neither reuse nor rebuild is decided'; exit 1) + 'jq' '--raw-output' '--exit-status' '. as $answer | [inputs] | if length != 0 then error("the cache-API answer is not exactly one JSON document") else $answer | if type == "object" and (.total_count | type) == "number" and (.actions_caches | type) == "array" and .total_count == (.actions_caches | length) and .total_count <= 100 and all(.actions_caches[]; type == "object" and (.key | type) == "string" and (.ref | type) == "string") then [.actions_caches[] | select(.key == $ENV.RELEASE_BINS_KEY and .ref == $ENV.RELEASE_BINS_DISPATCH_SCOPE_REF)] | if length == 0 then "absent" elif length == 1 then "present\nid=\(.[0].id) ref=\(.[0].ref) version=\(.[0].version) created=\(.[0].created_at)" else error("duplicate exact entries") end else error("cache-API answer is not the admitted shape") end end' '.release-bins-lookup-dispatch.json' > "$RUNNER_TEMP"'/lookup-dispatch.decoded' || ('echo' '::error title=release-bins LookupAnswerUnusable::the cache API answer was unreadable or incomplete; it is not treated as absence'; exit 1) + DECODED_STANDING=$('head' '-n' '1' "$RUNNER_TEMP"'/lookup-dispatch.decoded') + if '[' "$DECODED_STANDING" '=' 'present' ']'; then STANDING='present'; ENTRY=$('sed' '-n' '2p' "$RUNNER_TEMP"'/lookup-dispatch.decoded'); else '[' "$DECODED_STANDING" '=' 'absent' ']' || ('echo' '::error title=release-bins LookupAnswerUnusable::the cache API answer was unreadable or incomplete; it is not treated as absence'; exit 1); fi + 'echo' 'standing='"$STANDING" | 'tee' '-a' "$GITHUB_OUTPUT" > '/dev/null' + 'echo' 'entry='"$ENTRY" | 'tee' '-a' "$GITHUB_OUTPUT" > '/dev/null' + 'echo' 'release-bins lookup: standing='"$STANDING"' entry='"$ENTRY" + env: + RELEASE_BINS_KEY: ${{ steps.release_bins_key.outputs.key }} + GH_TOKEN: ${{ github.token }} + RELEASE_BINS_DISPATCH_SCOPE_REF: ${{ startsWith(github.ref, 'refs/tags/') && format('refs/heads/{0}', github.ref) || github.ref }} + timeout-minutes: 5 + - name: Restore release-bins into isolated staging (transport only; publishes on a verified build) + id: release_bins_cache + uses: actions/cache@caa296126883cff596d87d8935842f9db880ef25 + with: + path: |- + .release-bins-staging/release-bins.tgz + .release-bins-staging/release-bins.preimage + .release-bins-staging/release-bins.manifest + key: ${{ steps.release_bins_key.outputs.key }} + timeout-minutes: 10 + - name: Decide the compiler pair outcome; verify a stored pack completely before any restored binary runs + id: release_bins_outcome + run: | + set -e + ROOT=$('git' 'rev-parse' '--show-toplevel') + STAGE="$ROOT"'/.release-bins-staging' + EXPECTED="$RUNNER_TEMP"'/release-bins.preimage.expected' + if '[' "$LOOKUP_STANDING" '=' 'present' ']' && '[' "$CACHE_HIT" '=' 'true' ']'; then ':' 'no-op'; else if '[' "$LOOKUP_STANDING" '=' 'present' ']' && '[' "$CACHE_HIT" '!=' 'true' ']'; then ('echo' '::error title=release-bins StoreTransportDisagreePresentMiss::the cache API holds an exact admitted entry for this key but the transport did not restore it exactly; refusing rather than rebuilding over a present entry'; exit 1); else if '[' "$LOOKUP_STANDING" '=' 'absent' ']' && '[' "$CACHE_HIT" '=' 'true' ']'; then ('echo' '::error title=release-bins StoreTransportDisagreeAbsentHit::the cache API holds no exact admitted entry for this key but the transport reported an exact hit; the store and its transport disagree'; exit 1); else if '[' "$LOOKUP_STANDING" '=' 'absent' ']' && '[' "$CACHE_HIT" '!=' 'true' ']'; then 'rm' '-rf' "$STAGE"; 'mkdir' '-p' "$STAGE"; 'echo' 'outcome=build' | 'tee' '-a' "$GITHUB_OUTPUT" > '/dev/null'; exit 0; else ('echo' '::error title=release-bins OutcomeInputsMissing::the lookup standing or the transport exactness is missing, so no outcome can be decided'; exit 1); fi; fi; fi; fi + '[' '-f' "$STAGE"'/release-bins.preimage' ']' || ('echo' '::error title=release-bins StoredEntryIncomplete::the stored entry is incomplete: release-bins.preimage is missing'; exit 1) + '[' '-f' "$STAGE"'/release-bins.manifest' ']' || ('echo' '::error title=release-bins StoredEntryIncomplete::the stored entry is incomplete: release-bins.manifest is missing'; exit 1) + '[' '-f' "$STAGE"'/release-bins.tgz' ']' || ('echo' '::error title=release-bins StoredEntryIncomplete::the stored entry is incomplete: release-bins.tgz is missing'; exit 1) + 'cmp' '-s' "$EXPECTED" "$STAGE"'/release-bins.preimage' || ('diff' "$EXPECTED" "$STAGE"'/release-bins.preimage' || ':' 'no-op'; ('echo' '::error title=release-bins StoredPreimageDiffers::the stored preimage differs from this run'\''s independently derived preimage'; exit 1)) + 'sha256sum' "$STAGE"'/release-bins.preimage' > "$RUNNER_TEMP"'/STORED_DIGEST.out' + STORED_DIGEST=$('cut' '-d' ' ' '-f' '1' "$RUNNER_TEMP"'/STORED_DIGEST.out') + '[' 'release-bins-'"$STORED_DIGEST" '=' "$RELEASE_BINS_KEY" ']' || ('echo' '::error title=release-bins StoredPreimageKeyMismatch::the stored preimage does not hash to the probed key'; exit 1) + 'printf' '%s' 'claim_executor + gunbc + build_diagnostics + claim_executor_checker_inputs + ' > "$RUNNER_TEMP"'/release-bins.roster' + 'tar' '-tzf' "$STAGE"'/release-bins.tgz' > "$RUNNER_TEMP"'/pack-listing.out' + 'cmp' '-s' "$RUNNER_TEMP"'/pack-listing.out' "$RUNNER_TEMP"'/release-bins.roster' || ('echo' '::error title=release-bins PackListingNotRoster::the stored pack'\''s archive listing is not exactly the roster (an extra, duplicate, directory or escaping entry)'; exit 1) + 'cut' '-c' '67-' "$STAGE"'/release-bins.manifest' > "$RUNNER_TEMP"'/manifest-names.out' + 'cmp' '-s' "$RUNNER_TEMP"'/manifest-names.out' "$RUNNER_TEMP"'/release-bins.roster' || ('echo' '::error title=release-bins ManifestRosterMismatch::the stored manifest does not name exactly the gunbc.ci_release_bins roster in order'; exit 1) + if 'grep' '-qvE' '^[0-9a-f]{64} [A-Za-z0-9_]+$' "$STAGE"'/release-bins.manifest'; then ('echo' '::error title=release-bins ManifestMalformed::the stored manifest has a line that is not one well-formed sha256 record for a roster member'; exit 1); fi + 'rm' '-rf' "$STAGE"'/unpacked' + 'mkdir' '-p' "$STAGE"'/unpacked' + 'tar' '-xzf' "$STAGE"'/release-bins.tgz' '-C' "$STAGE"'/unpacked' '--no-same-owner' + 'printf' '%s' 'claim_executor:regular file:1 + gunbc:regular file:1 + ' > "$RUNNER_TEMP"'/member-types.expected' + ('cd' "$STAGE"'/unpacked' && 'stat' '-c' '%n:%F:%h' 'claim_executor' 'gunbc') > "$RUNNER_TEMP"'/member-types.out' + 'cmp' '-s' "$RUNNER_TEMP"'/member-types.out' "$RUNNER_TEMP"'/member-types.expected' || ('diff' "$RUNNER_TEMP"'/member-types.expected' "$RUNNER_TEMP"'/member-types.out' || ':' 'no-op'; ('echo' '::error title=release-bins MemberNotRegularFile::the stored pack has a member that is not a self-contained regular file (a link, special file or hard-link dependency; the report above names it)'; exit 1)) + ('cd' "$STAGE"'/unpacked' && 'find' 'claim_executor' 'gunbc' '-maxdepth' '0' '!' '-perm' '-u+x') > "$RUNNER_TEMP"'/member-nonexec.out' + '[' '!' '-s' "$RUNNER_TEMP"'/member-nonexec.out' ']' || ('cat' "$RUNNER_TEMP"'/member-nonexec.out'; ('echo' '::error title=release-bins MemberNotExecutable::the stored pack has a member that is not executable (listed above)'; exit 1)) + ('cd' "$STAGE"'/unpacked' && 'sha256sum' '--strict' '--quiet' '-c' "$STAGE"'/release-bins.manifest') || ('echo' '::error title=release-bins MemberDigestMismatch::the stored member bytes do not match the manifest under strict checking; refusing before any member is promoted or run'; exit 1) + 'mkdir' '-p' "$ROOT"'/target/release' + ('cd' "$ROOT"'/target/release' && 'rm' '-f' 'claim_executor' 'gunbc') + ('cd' "$STAGE"'/unpacked' && 'cp' '-p' 'claim_executor' 'gunbc' "$ROOT"'/target/release/') + ('cd' "$STAGE"'/unpacked' && 'cp' '-p' 'claim_executor_checker_inputs' "$ROOT"'/target/release/') + 'rm' '-rf' "$STAGE"'/unpacked' + 'echo' 'release-bins: stored pack verified and promoted' + 'sha256sum' "$STAGE"'/release-bins.manifest' > "$RUNNER_TEMP"'/OUTPUT_DIGEST.out' + OUTPUT_DIGEST=$('cut' '-d' ' ' '-f' '1' "$RUNNER_TEMP"'/OUTPUT_DIGEST.out') + 'echo' 'ReleaseBinsReuseReceipt: request='"$RELEASE_BINS_KEY"' output=sha256:'"$OUTPUT_DIGEST"' dispatch='"$GITHUB_RUN_ID"'/'"$GITHUB_RUN_ATTEMPT"' producer=['"$LOOKUP_ENTRY"'] built=no' + 'echo' 'outcome=reuse' | 'tee' '-a' "$GITHUB_OUTPUT" > '/dev/null' + env: + RELEASE_BINS_KEY: ${{ steps.release_bins_key.outputs.key }} + LOOKUP_STANDING: ${{ steps.release_bins_lookup.outputs.standing }} + LOOKUP_ENTRY: ${{ steps.release_bins_lookup.outputs.entry }} + CACHE_HIT: ${{ steps.release_bins_cache.outputs.cache-hit }} + timeout-minutes: 5 + - name: Cache Cargo + uses: actions/cache@caa296126883cff596d87d8935842f9db880ef25 + with: + path: |- + ${{ runner.temp }}/cargo/registry/index/ + ${{ runner.temp }}/cargo/registry/cache/ + ${{ runner.temp }}/cargo/git/db/ + target/ + key: cargo-ci-${{ runner.os }}-${{ runner.arch }}-${{ hashFiles('**/Cargo.lock', 'rust-toolchain.toml') }} + restore-keys: | + cargo-ci-${{ runner.os }}-${{ runner.arch }}- + if: steps.release_bins_outcome.outputs.outcome != 'reuse' + - name: Build the compiler pair (keyed miss only) + id: build_witness_fold + run: |+ + GUNBC_FLOOR_LOG='gunbc-floor-cmd.log' + 'set' '+e' + 'set' '-o' 'pipefail' + ('sh' '-e' '-c' 'cargo build --release -p v1-compiler --bin claim_executor --bin gunbc') 2>&1 | 'tee' "$GUNBC_FLOOR_LOG" + GUNBC_FLOOR_EXIT="$?" + GUNBC_FLOOR_RECEIPT='gunbc-floor-outcome.txt' + GUNBC_FLOOR_CLASS='structural' + GUNBC_FLOOR_SIGNATURE='' + if '[' "$GUNBC_FLOOR_EXIT" '-eq' '126' ']'; then GUNBC_FLOOR_CLASS='infra'; GUNBC_FLOOR_SIGNATURE='CommandInvokedCannotExecute'; fi + if '[' "$GUNBC_FLOOR_CLASS" '=' 'structural' ']' && '[' "$GUNBC_FLOOR_EXIT" '-eq' '127' ']'; then GUNBC_FLOOR_CLASS='infra'; GUNBC_FLOOR_SIGNATURE='CommandNotFound'; fi + if '[' "$GUNBC_FLOOR_CLASS" '=' 'structural' ']' && '[' "$GUNBC_FLOOR_EXIT" '-eq' '0' ']'; then GUNBC_FLOOR_CLASS='none'; GUNBC_FLOOR_SIGNATURE=''; fi + if '[' "$GUNBC_FLOOR_CLASS" '=' 'structural' ']' && '[' '-f' "$GUNBC_FLOOR_LOG" ']' && 'grep' '-q' 'MemoryStallRefusedPageThrash' "$GUNBC_FLOOR_LOG"; then GUNBC_FLOOR_CLASS='infra'; GUNBC_FLOOR_SIGNATURE='MemoryStallRefusedPageThrash'; fi + if '[' "$GUNBC_FLOOR_CLASS" '=' 'structural' ']' && '[' '-f' "$GUNBC_FLOOR_LOG" ']' && 'grep' '-q' 'The runner has received a shutdown signal' "$GUNBC_FLOOR_LOG"; then GUNBC_FLOOR_CLASS='infra'; GUNBC_FLOOR_SIGNATURE='RunnerLost'; fi + if '[' "$GUNBC_FLOOR_CLASS" '=' 'structural' ']' && '[' '-f' "$GUNBC_FLOOR_LOG" ']' && 'grep' '-q' 'sccache: error: failed to execute compile' "$GUNBC_FLOOR_LOG"; then GUNBC_FLOOR_CLASS='infra'; GUNBC_FLOOR_SIGNATURE='SccacheFailedToExecuteCompile'; fi + if '[' "$GUNBC_FLOOR_CLASS" '=' 'structural' ']' && '[' '-f' "$GUNBC_FLOOR_LOG" ']' && 'grep' '-q' '(exit status: 254)' "$GUNBC_FLOOR_LOG"; then GUNBC_FLOOR_CLASS='infra'; GUNBC_FLOOR_SIGNATURE='SccacheRustcWrapperExit254'; fi + if '[' "$GUNBC_FLOOR_CLASS" '=' 'structural' ']' && '[' '-f' "$GUNBC_FLOOR_LOG" ']' && 'grep' '-q' 'sccache: encountered fatal error' "$GUNBC_FLOOR_LOG"; then GUNBC_FLOOR_CLASS='infra'; GUNBC_FLOOR_SIGNATURE='SccacheFatalError'; fi + if '[' "$GUNBC_FLOOR_CLASS" '=' 'structural' ']' && '[' '-f' "$GUNBC_FLOOR_LOG" ']' && 'grep' '-q' 'failed to spawn' "$GUNBC_FLOOR_LOG"; then GUNBC_FLOOR_CLASS='infra'; GUNBC_FLOOR_SIGNATURE='ProcessSpawnFailure'; fi + if '[' "$GUNBC_FLOOR_CLASS" '=' 'structural' ']' && '[' '-f' "$GUNBC_FLOOR_LOG" ']' && 'grep' '-q' 'Resource temporarily unavailable' "$GUNBC_FLOOR_LOG"; then GUNBC_FLOOR_CLASS='infra'; GUNBC_FLOOR_SIGNATURE='ResourceTemporarilyUnavailable'; fi + if (! '[' '-f' "$GUNBC_FLOOR_RECEIPT" ']') || '[' "$GUNBC_FLOOR_CLASS" '=' 'structural' ']' || ('[' "$GUNBC_FLOOR_CLASS" '=' 'infra' ']' && (! ('[' '-f' "$GUNBC_FLOOR_RECEIPT" ']' && 'grep' '-q' 'class=structural' "$GUNBC_FLOOR_RECEIPT"))) || ('[' "$GUNBC_FLOOR_CLASS" '=' 'none' ']' && (! ('[' '-f' "$GUNBC_FLOOR_RECEIPT" ']' && 'grep' '-q' 'class=structural' "$GUNBC_FLOOR_RECEIPT")) && (! ('[' '-f' "$GUNBC_FLOOR_RECEIPT" ']' && 'grep' '-q' 'class=infra' "$GUNBC_FLOOR_RECEIPT"))); then 'printf' 'class=%s\nsignature=%s\nexit=%s\n' "$GUNBC_FLOOR_CLASS" "$GUNBC_FLOOR_SIGNATURE" "$GUNBC_FLOOR_EXIT" > "$GUNBC_FLOOR_RECEIPT"; if '[' "$GUNBC_FLOOR_CLASS" '=' 'infra' ']'; then 'echo' '::error title=environment::floor_class='"$GUNBC_FLOOR_CLASS"' signature='"$GUNBC_FLOOR_SIGNATURE"' exit='"$GUNBC_FLOOR_EXIT"'; this is not a verdict about the diff. Attempt receipt: '"$GUNBC_FLOOR_RECEIPT"; fi; if '[' "$GUNBC_FLOOR_CLASS" '=' 'structural' ']'; then 'echo' '::error title=subject::floor_class='"$GUNBC_FLOOR_CLASS"' exit='"$GUNBC_FLOOR_EXIT"'; read the step log for the subject defect'; fi; fi + 'exit' "$GUNBC_FLOOR_EXIT" + + if: steps.release_bins_outcome.outputs.outcome != 'reuse' + - name: Pack the compiler pair with its build log (build outcomes only) + run: | + set -e + ROOT=$('git' 'rev-parse' '--show-toplevel') + STAGE="$ROOT"'/.release-bins-staging' + 'mkdir' '-p' "$STAGE" + '[' '-s' "$ROOT"'/gunbc-floor-cmd.log' ']' || ('echo' '::error title=release-bins BuildDiagnosticsMissing::the product requires the build log gunbc-floor-cmd.log as a pack member and the build step left none; a pack without its diagnostics is incomplete, not smaller'; exit 1) + 'cp' "$ROOT"'/gunbc-floor-cmd.log' "$ROOT"'/target/release/build_diagnostics' + './target/release/claim_executor' '--write-checker-inputs' "$ROOT"'/target/release/claim_executor_checker_inputs' + '[' '-s' "$ROOT"'/target/release/claim_executor_checker_inputs' ']' || ('echo' '::error title=release-bins SidecarMissing::the product requires the sidecar claim_executor_checker_inputs as a pack member and the producing job left none; a pack without it is incomplete, not smaller'; exit 1) + ('cd' "$ROOT"'/target/release' && 'sha256sum' 'claim_executor' 'gunbc' 'build_diagnostics' 'claim_executor_checker_inputs') > "$STAGE"'/release-bins.manifest' + 'tar' '-czf' "$STAGE"'/release-bins.tgz' '-C' "$ROOT"'/target/release' 'claim_executor' 'gunbc' 'build_diagnostics' 'claim_executor_checker_inputs' + 'cp' "$RUNNER_TEMP"'/release-bins.preimage.expected' "$STAGE"'/release-bins.preimage' + 'sha256sum' "$STAGE"'/release-bins.manifest' > "$RUNNER_TEMP"'/OUTPUT_DIGEST.out' + OUTPUT_DIGEST=$('cut' '-d' ' ' '-f' '1' "$RUNNER_TEMP"'/OUTPUT_DIGEST.out') + 'echo' 'ReleaseBinsBuildReceipt: request='"$RELEASE_BINS_KEY"' output=sha256:'"$OUTPUT_DIGEST"' dispatch='"$GITHUB_RUN_ID"'/'"$GITHUB_RUN_ATTEMPT"' built=yes publish=requested-not-yet-persisted scope='"$GITHUB_REF" + env: + RELEASE_BINS_KEY: ${{ steps.release_bins_key.outputs.key }} + if: steps.release_bins_outcome.outputs.outcome != 'reuse' + timeout-minutes: 5 + - name: Upload release-bins artifact + uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f + with: + name: release-bins + path: |- + .release-bins-staging/release-bins.tgz + .release-bins-staging/release-bins.preimage + .release-bins-staging/release-bins.manifest + if-no-files-found: error + retention-days: 3 + compression-level: 0 + timeout-minutes: 10 + - name: Wrap the compiler pair as the merge-group candidate object + run: | + set -e + ROOT=$('git' 'rev-parse' '--show-toplevel') + STAGE="$ROOT"'/.release-bins-staging' + 'tar' '-cf' "$RUNNER_TEMP"'/pack.tar' '-C' "$STAGE" 'release-bins.preimage' 'release-bins.manifest' 'release-bins.tgz' + if: github.event_name == 'merge_group' + timeout-minutes: 5 + - name: Upload the compiler pair candidate + uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f + with: + name: compiler-pair-candidate + path: ${{ runner.temp }}/pack.tar + if-no-files-found: error + retention-days: 1 + compression-level: 0 + if: github.event_name == 'merge_group' + - name: Require isolated toolchain homes + run: | + if [ -z "${CARGO_HOME:-}" ] || [ -z "${RUSTUP_HOME:-}" ]; then echo "::error::ToolchainHomesNotIsolated CARGO_HOME=${CARGO_HOME:-unset} RUSTUP_HOME=${RUSTUP_HOME:-unset} -- the isolation step did not run, so this job shares a toolchain with every other runner slot on this host and a concurrent install can replace a binary mid-run" >&2; exit 1; fi + if: "!cancelled()" + env: + MALLOC_ARENA_MAX: "2" emit-build: runs-on: [self-hosted, linux, arm64] timeout-minutes: 90 @@ -199,6 +497,7 @@ jobs: MALLOC_ARENA_MAX: "2" floor: runs-on: [self-hosted, linux, arm64] + needs: [seed] timeout-minutes: 90 if: github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == github.repository permissions: @@ -230,143 +529,60 @@ jobs: if [ -x "$CARGO_HOME/bin/cargo" ]; then CARGO_BIN="$CARGO_HOME/bin/cargo"; else CARGO_BIN="$(command -v cargo || true)"; fi if [ -z "$CARGO_BIN" ]; then echo "::error::no cargo binary: neither the isolated $CARGO_HOME/bin/cargo shim nor PATH carries one"; exit 1; fi echo "CARGO_BIN=$CARGO_BIN" >> "$GITHUB_ENV" - - name: Key the compiler pair candidate (merge group; stamps the build with the materialized tree) + - name: Download release-bins artifact + uses: actions/download-artifact@37930b1c2abaa49bbe596cd826c3c89aef350131 + with: + name: release-bins + path: ${{ runner.temp }}/release-bins-download + timeout-minutes: 10 + - name: Verify the seed's compiler pair against its key and promote it into this checkout + id: build_witness_fold run: | set -e ROOT=$('git' 'rev-parse' '--show-toplevel') - DIRTY=$('git' '-C' "$ROOT" 'status' '--porcelain' '--untracked-files=no') - '[' '-z' "$DIRTY" ']' || ('echo' '::error title=release-bins SourceTreeDirty::tracked files differ from HEAD, so HEAD^{tree} does not name the build input; refusing to key'; exit 1) - 'env' '-0' > "$RUNNER_TEMP"'/env.nul' - if 'grep' '-zqE' '^(CARGO|RUST|CC|CXX|AR|CFLAGS|CXXFLAGS|LDFLAGS|TARGET_CC|HOST_CC)[A-Za-z0-9_]*(TOKEN|CREDENTIAL|SECRET|PASSWORD|PASSWD|AUTH)[A-Za-z0-9_]*=' "$RUNNER_TEMP"'/env.nul'; then ('echo' '::error title=release-bins CredentialInBuildEnvironment::a registry or toolchain credential is present in the build environment; credentials are not build inputs and must not reach a key or a published preimage, so the build is refused'; exit 1); fi - if 'grep' '-zqE' '^(RUSTC|RUSTC_WORKSPACE_WRAPPER|CARGO_BUILD_RUSTC|CARGO_BUILD_RUSTC_WRAPPER|CARGO_BUILD_RUSTC_WORKSPACE_WRAPPER|CARGO_BUILD_TARGET|CARGO_BUILD_TARGET_DIR|CARGO_TARGET_DIR|CARGO_TARGET_[A-Z0-9_]*_(LINKER|RUNNER))=' "$RUNNER_TEMP"'/env.nul'; then ('echo' '::error title=release-bins BuildOverrideNotAdmitted::the environment overrides the compiler, linker, target or target directory Cargo would use; the key probes only the admitted defaults, so the build is refused rather than keyed wrongly'; exit 1); fi - 'sed' '-z' '-nE' '/^(CARGO_[A-Z0-9_]*|RUST[A-Z0-9_]*|CC|CXX|AR|CFLAGS|CXXFLAGS|LDFLAGS|CC_[A-Za-z0-9_]*|CFLAGS_[A-Za-z0-9_]*|TARGET_CC|HOST_CC)=/p' "$RUNNER_TEMP"'/env.nul' > "$RUNNER_TEMP"'/env-keyed.nul' - 'sed' '-z' '-E' '/^(CARGO_HOME|RUSTUP_HOME|CARGO_BIN|CARGO_TERM_COLOR|CARGO_BUILD_JOBS|RUSTC_WRAPPER)=/d' "$RUNNER_TEMP"'/env-keyed.nul' > "$RUNNER_TEMP"'/env-kept.nul' - 'sort' '-z' "$RUNNER_TEMP"'/env-kept.nul' > "$RUNNER_TEMP"'/env-sorted.nul' - 'sha256sum' "$RUNNER_TEMP"'/env-sorted.nul' > "$RUNNER_TEMP"'/AXIS_ENVIRONMENT_DIGEST.out' - AXIS_ENVIRONMENT_DIGEST=$('cut' '-d' ' ' '-f' '1' "$RUNNER_TEMP"'/AXIS_ENVIRONMENT_DIGEST.out') - ANCESTOR_0="$ROOT" - ANCESTOR_CONFIG='' - ANCESTOR_1=$('dirname' "$ANCESTOR_0") - ('[' '!' '-e' "$ANCESTOR_1"'/.cargo/config.toml' ']' && '[' '!' '-e' "$ANCESTOR_1"'/.cargo/config' ']') || ANCESTOR_CONFIG="$ANCESTOR_1" - ANCESTOR_2=$('dirname' "$ANCESTOR_1") - ('[' '!' '-e' "$ANCESTOR_2"'/.cargo/config.toml' ']' && '[' '!' '-e' "$ANCESTOR_2"'/.cargo/config' ']') || ANCESTOR_CONFIG="$ANCESTOR_2" - ANCESTOR_3=$('dirname' "$ANCESTOR_2") - ('[' '!' '-e' "$ANCESTOR_3"'/.cargo/config.toml' ']' && '[' '!' '-e' "$ANCESTOR_3"'/.cargo/config' ']') || ANCESTOR_CONFIG="$ANCESTOR_3" - ANCESTOR_4=$('dirname' "$ANCESTOR_3") - ('[' '!' '-e' "$ANCESTOR_4"'/.cargo/config.toml' ']' && '[' '!' '-e' "$ANCESTOR_4"'/.cargo/config' ']') || ANCESTOR_CONFIG="$ANCESTOR_4" - ANCESTOR_5=$('dirname' "$ANCESTOR_4") - ('[' '!' '-e' "$ANCESTOR_5"'/.cargo/config.toml' ']' && '[' '!' '-e' "$ANCESTOR_5"'/.cargo/config' ']') || ANCESTOR_CONFIG="$ANCESTOR_5" - ANCESTOR_6=$('dirname' "$ANCESTOR_5") - ('[' '!' '-e' "$ANCESTOR_6"'/.cargo/config.toml' ']' && '[' '!' '-e' "$ANCESTOR_6"'/.cargo/config' ']') || ANCESTOR_CONFIG="$ANCESTOR_6" - ANCESTOR_7=$('dirname' "$ANCESTOR_6") - ('[' '!' '-e' "$ANCESTOR_7"'/.cargo/config.toml' ']' && '[' '!' '-e' "$ANCESTOR_7"'/.cargo/config' ']') || ANCESTOR_CONFIG="$ANCESTOR_7" - ANCESTOR_8=$('dirname' "$ANCESTOR_7") - ('[' '!' '-e' "$ANCESTOR_8"'/.cargo/config.toml' ']' && '[' '!' '-e' "$ANCESTOR_8"'/.cargo/config' ']') || ANCESTOR_CONFIG="$ANCESTOR_8" - ANCESTOR_9=$('dirname' "$ANCESTOR_8") - ('[' '!' '-e' "$ANCESTOR_9"'/.cargo/config.toml' ']' && '[' '!' '-e' "$ANCESTOR_9"'/.cargo/config' ']') || ANCESTOR_CONFIG="$ANCESTOR_9" - ANCESTOR_10=$('dirname' "$ANCESTOR_9") - ('[' '!' '-e' "$ANCESTOR_10"'/.cargo/config.toml' ']' && '[' '!' '-e' "$ANCESTOR_10"'/.cargo/config' ']') || ANCESTOR_CONFIG="$ANCESTOR_10" - ANCESTOR_11=$('dirname' "$ANCESTOR_10") - ('[' '!' '-e' "$ANCESTOR_11"'/.cargo/config.toml' ']' && '[' '!' '-e' "$ANCESTOR_11"'/.cargo/config' ']') || ANCESTOR_CONFIG="$ANCESTOR_11" - ANCESTOR_12=$('dirname' "$ANCESTOR_11") - ('[' '!' '-e' "$ANCESTOR_12"'/.cargo/config.toml' ']' && '[' '!' '-e' "$ANCESTOR_12"'/.cargo/config' ']') || ANCESTOR_CONFIG="$ANCESTOR_12" - ANCESTOR_13=$('dirname' "$ANCESTOR_12") - ('[' '!' '-e' "$ANCESTOR_13"'/.cargo/config.toml' ']' && '[' '!' '-e' "$ANCESTOR_13"'/.cargo/config' ']') || ANCESTOR_CONFIG="$ANCESTOR_13" - ANCESTOR_14=$('dirname' "$ANCESTOR_13") - ('[' '!' '-e' "$ANCESTOR_14"'/.cargo/config.toml' ']' && '[' '!' '-e' "$ANCESTOR_14"'/.cargo/config' ']') || ANCESTOR_CONFIG="$ANCESTOR_14" - ANCESTOR_15=$('dirname' "$ANCESTOR_14") - ('[' '!' '-e' "$ANCESTOR_15"'/.cargo/config.toml' ']' && '[' '!' '-e' "$ANCESTOR_15"'/.cargo/config' ']') || ANCESTOR_CONFIG="$ANCESTOR_15" - ANCESTOR_16=$('dirname' "$ANCESTOR_15") - ('[' '!' '-e' "$ANCESTOR_16"'/.cargo/config.toml' ']' && '[' '!' '-e' "$ANCESTOR_16"'/.cargo/config' ']') || ANCESTOR_CONFIG="$ANCESTOR_16" - if '[' '-n' "$ANCESTOR_CONFIG" ']'; then 'echo' 'release-bins: Cargo config found above the checkout in '"$ANCESTOR_CONFIG"; ('echo' '::error title=release-bins AncestorCargoConfigPresent::a Cargo config file exists in a directory above the checkout; it is outside the committed tree the key covers, so the build is refused'; exit 1); fi - '[' "$ANCESTOR_16" '=' '/' ']' || ('echo' '::error title=release-bins CheckoutPathTooDeep::the checkout is deeper than the ancestor Cargo-config scan covers, so ancestor configuration cannot be ruled out'; exit 1) + '[' '-n' "$RELEASE_BINS_KEY" ']' || ('echo' '::error title=release-bins ConsumerKeyMissing::the build job published no release-bins key'; exit 1) + '[' '-f' "$RUNNER_TEMP"'/release-bins-download/release-bins.preimage' ']' || ('echo' '::error title=release-bins ConsumerFileMissing::the release-bins artifact is missing release-bins.preimage'; exit 1) + '[' '-f' "$RUNNER_TEMP"'/release-bins-download/release-bins.manifest' ']' || ('echo' '::error title=release-bins ConsumerFileMissing::the release-bins artifact is missing release-bins.manifest'; exit 1) + '[' '-f' "$RUNNER_TEMP"'/release-bins-download/release-bins.tgz' ']' || ('echo' '::error title=release-bins ConsumerFileMissing::the release-bins artifact is missing release-bins.tgz'; exit 1) + 'sha256sum' "$RUNNER_TEMP"'/release-bins-download/release-bins.preimage' > "$RUNNER_TEMP"'/DOWNLOADED_DIGEST.out' + DOWNLOADED_DIGEST=$('cut' '-d' ' ' '-f' '1' "$RUNNER_TEMP"'/DOWNLOADED_DIGEST.out') + '[' 'release-bins-'"$DOWNLOADED_DIGEST" '=' "$RELEASE_BINS_KEY" ']' || ('echo' '::error title=release-bins ConsumerPreimageKeyMismatch::the downloaded preimage does not hash to the build job'\''s key'; exit 1) OBJECT_FORMAT=$('git' '-C' "$ROOT" 'rev-parse' '--show-object-format') SOURCE_TREE=$('git' '-C' "$ROOT" 'rev-parse' 'HEAD^{tree}') '[' '-n' "$OBJECT_FORMAT" ']' || ('echo' '::error title=release-bins AxisValueUnread::the key input OBJECT_FORMAT read back empty, so the key would not name the build; refusing to key'; exit 1) '[' '-n' "$SOURCE_TREE" ']' || ('echo' '::error title=release-bins AxisValueUnread::the key input SOURCE_TREE read back empty, so the key would not name the build; refusing to key'; exit 1) SOURCE_CLOSURE='tree:'"$OBJECT_FORMAT"':'"$SOURCE_TREE" - EXPECTED="$RUNNER_TEMP"'/release-bins.preimage.expected' - AXIS_PRODUCER=$("$CARGO_BIN" '-V') - '[' '-n' "$AXIS_PRODUCER" ']' || ('echo' '::error title=release-bins AxisValueUnread::the key input AXIS_PRODUCER read back empty, so the key would not name the build; refusing to key'; exit 1) - 'rustc' '-vV' > "$RUNNER_TEMP"'/rustc-host.out' - AXIS_HOST=$('sed' '-n' 's/^host: //p' "$RUNNER_TEMP"'/rustc-host.out') - 'cc' '--version' > "$RUNNER_TEMP"'/AXIS_CC.out' - AXIS_CC=$('head' '-n' '1' "$RUNNER_TEMP"'/AXIS_CC.out') - 'ld' '--version' > "$RUNNER_TEMP"'/AXIS_LD.out' - AXIS_LD=$('head' '-n' '1' "$RUNNER_TEMP"'/AXIS_LD.out') - 'ldd' '--version' > "$RUNNER_TEMP"'/AXIS_LIBC.out' - AXIS_LIBC=$('head' '-n' '1' "$RUNNER_TEMP"'/AXIS_LIBC.out') - '[' '-n' "$AXIS_HOST" ']' || ('echo' '::error title=release-bins AxisValueUnread::the key input AXIS_HOST read back empty, so the key would not name the build; refusing to key'; exit 1) - '[' '-n' "$AXIS_CC" ']' || ('echo' '::error title=release-bins AxisValueUnread::the key input AXIS_CC read back empty, so the key would not name the build; refusing to key'; exit 1) - '[' '-n' "$AXIS_LD" ']' || ('echo' '::error title=release-bins AxisValueUnread::the key input AXIS_LD read back empty, so the key would not name the build; refusing to key'; exit 1) - '[' '-n' "$AXIS_LIBC" ']' || ('echo' '::error title=release-bins AxisValueUnread::the key input AXIS_LIBC read back empty, so the key would not name the build; refusing to key'; exit 1) - 'rustc' '-vV' > "$RUNNER_TEMP"'/rustc-vV.out' - AXIS_TOOLCHAIN=$('paste' '-s' '-d' ';' "$RUNNER_TEMP"'/rustc-vV.out') - '[' '-n' "$AXIS_TOOLCHAIN" ']' || ('echo' '::error title=release-bins AxisValueUnread::the key input AXIS_TOOLCHAIN read back empty, so the key would not name the build; refusing to key'; exit 1) - ('printf' 'kind=%s\n' 'gunbc.compiler_pair_release_bins'; 'printf' 'address=sha256 scope=cross_run\n'; 'printf' 'producer_compiler=%s\n' "$AXIS_PRODUCER"; 'printf' 'source_closure=%s\n' "$SOURCE_CLOSURE"; 'printf' 'target_model=%s;cc=%s;ld=%s;libc=%s\n' "$AXIS_HOST" "$AXIS_CC" "$AXIS_LD" "$AXIS_LIBC"; 'printf' 'toolchain=%s\n' "$AXIS_TOOLCHAIN"; 'printf' 'build_configuration=%s;embedded_build_identity=%s;environment=%s;roster=%s\n' 'cargo build --release -p v1-compiler --bin claim_executor --bin gunbc' "$SOURCE_CLOSURE" 'sha256:'"$AXIS_ENVIRONMENT_DIGEST" 'claim_executor gunbc'; 'printf' 'required_lens_contract=none (a seed cargo build admits no lens contract)\n') > "$EXPECTED" - 'sha256sum' "$EXPECTED" > "$RUNNER_TEMP"'/PREIMAGE_DIGEST.out' - PREIMAGE_DIGEST=$('cut' '-d' ' ' '-f' '1' "$RUNNER_TEMP"'/PREIMAGE_DIGEST.out') - KEY='release-bins-'"$PREIMAGE_DIGEST" - 'rm' '-rf' "$ROOT"'/.release-bins-staging' - 'mkdir' '-p' "$ROOT"'/.release-bins-staging' - 'echo' 'release-bins request key '"$KEY"' over preimage:' - 'cat' "$EXPECTED" - 'echo' 'key='"$KEY" | 'tee' '-a' "$GITHUB_OUTPUT" > '/dev/null' - 'echo' 'GUNBC_MATERIALIZED_TREE_IDENTITY='"$SOURCE_CLOSURE" | 'tee' '-a' "$GITHUB_ENV" > '/dev/null' - if: github.event_name == 'merge_group' - - name: Build the compiler and the witness executor - id: build_witness_fold - run: |+ - GUNBC_FLOOR_LOG='gunbc-floor-cmd.log' - 'set' '+e' - 'set' '-o' 'pipefail' - ('sh' '-e' '-c' 'cargo build --release -p v1-compiler --bin claim_executor --bin gunbc') 2>&1 | 'tee' "$GUNBC_FLOOR_LOG" - GUNBC_FLOOR_EXIT="$?" - GUNBC_FLOOR_RECEIPT='gunbc-floor-outcome.txt' - GUNBC_FLOOR_CLASS='structural' - GUNBC_FLOOR_SIGNATURE='' - if '[' "$GUNBC_FLOOR_EXIT" '-eq' '126' ']'; then GUNBC_FLOOR_CLASS='infra'; GUNBC_FLOOR_SIGNATURE='CommandInvokedCannotExecute'; fi - if '[' "$GUNBC_FLOOR_CLASS" '=' 'structural' ']' && '[' "$GUNBC_FLOOR_EXIT" '-eq' '127' ']'; then GUNBC_FLOOR_CLASS='infra'; GUNBC_FLOOR_SIGNATURE='CommandNotFound'; fi - if '[' "$GUNBC_FLOOR_CLASS" '=' 'structural' ']' && '[' "$GUNBC_FLOOR_EXIT" '-eq' '0' ']'; then GUNBC_FLOOR_CLASS='none'; GUNBC_FLOOR_SIGNATURE=''; fi - if '[' "$GUNBC_FLOOR_CLASS" '=' 'structural' ']' && '[' '-f' "$GUNBC_FLOOR_LOG" ']' && 'grep' '-q' 'MemoryStallRefusedPageThrash' "$GUNBC_FLOOR_LOG"; then GUNBC_FLOOR_CLASS='infra'; GUNBC_FLOOR_SIGNATURE='MemoryStallRefusedPageThrash'; fi - if '[' "$GUNBC_FLOOR_CLASS" '=' 'structural' ']' && '[' '-f' "$GUNBC_FLOOR_LOG" ']' && 'grep' '-q' 'The runner has received a shutdown signal' "$GUNBC_FLOOR_LOG"; then GUNBC_FLOOR_CLASS='infra'; GUNBC_FLOOR_SIGNATURE='RunnerLost'; fi - if '[' "$GUNBC_FLOOR_CLASS" '=' 'structural' ']' && '[' '-f' "$GUNBC_FLOOR_LOG" ']' && 'grep' '-q' 'sccache: error: failed to execute compile' "$GUNBC_FLOOR_LOG"; then GUNBC_FLOOR_CLASS='infra'; GUNBC_FLOOR_SIGNATURE='SccacheFailedToExecuteCompile'; fi - if '[' "$GUNBC_FLOOR_CLASS" '=' 'structural' ']' && '[' '-f' "$GUNBC_FLOOR_LOG" ']' && 'grep' '-q' '(exit status: 254)' "$GUNBC_FLOOR_LOG"; then GUNBC_FLOOR_CLASS='infra'; GUNBC_FLOOR_SIGNATURE='SccacheRustcWrapperExit254'; fi - if '[' "$GUNBC_FLOOR_CLASS" '=' 'structural' ']' && '[' '-f' "$GUNBC_FLOOR_LOG" ']' && 'grep' '-q' 'sccache: encountered fatal error' "$GUNBC_FLOOR_LOG"; then GUNBC_FLOOR_CLASS='infra'; GUNBC_FLOOR_SIGNATURE='SccacheFatalError'; fi - if '[' "$GUNBC_FLOOR_CLASS" '=' 'structural' ']' && '[' '-f' "$GUNBC_FLOOR_LOG" ']' && 'grep' '-q' 'failed to spawn' "$GUNBC_FLOOR_LOG"; then GUNBC_FLOOR_CLASS='infra'; GUNBC_FLOOR_SIGNATURE='ProcessSpawnFailure'; fi - if '[' "$GUNBC_FLOOR_CLASS" '=' 'structural' ']' && '[' '-f' "$GUNBC_FLOOR_LOG" ']' && 'grep' '-q' 'Resource temporarily unavailable' "$GUNBC_FLOOR_LOG"; then GUNBC_FLOOR_CLASS='infra'; GUNBC_FLOOR_SIGNATURE='ResourceTemporarilyUnavailable'; fi - if (! '[' '-f' "$GUNBC_FLOOR_RECEIPT" ']') || '[' "$GUNBC_FLOOR_CLASS" '=' 'structural' ']' || ('[' "$GUNBC_FLOOR_CLASS" '=' 'infra' ']' && (! ('[' '-f' "$GUNBC_FLOOR_RECEIPT" ']' && 'grep' '-q' 'class=structural' "$GUNBC_FLOOR_RECEIPT"))) || ('[' "$GUNBC_FLOOR_CLASS" '=' 'none' ']' && (! ('[' '-f' "$GUNBC_FLOOR_RECEIPT" ']' && 'grep' '-q' 'class=structural' "$GUNBC_FLOOR_RECEIPT")) && (! ('[' '-f' "$GUNBC_FLOOR_RECEIPT" ']' && 'grep' '-q' 'class=infra' "$GUNBC_FLOOR_RECEIPT"))); then 'printf' 'class=%s\nsignature=%s\nexit=%s\n' "$GUNBC_FLOOR_CLASS" "$GUNBC_FLOOR_SIGNATURE" "$GUNBC_FLOOR_EXIT" > "$GUNBC_FLOOR_RECEIPT"; if '[' "$GUNBC_FLOOR_CLASS" '=' 'infra' ']'; then 'echo' '::error title=environment::floor_class='"$GUNBC_FLOOR_CLASS"' signature='"$GUNBC_FLOOR_SIGNATURE"' exit='"$GUNBC_FLOOR_EXIT"'; this is not a verdict about the diff. Attempt receipt: '"$GUNBC_FLOOR_RECEIPT"; fi; if '[' "$GUNBC_FLOOR_CLASS" '=' 'structural' ']'; then 'echo' '::error title=subject::floor_class='"$GUNBC_FLOOR_CLASS"' exit='"$GUNBC_FLOOR_EXIT"'; read the step log for the subject defect'; fi; fi - 'exit' "$GUNBC_FLOOR_EXIT" - - - name: Pack the compiler pair candidate with its build log - run: | - set -e - ROOT=$('git' 'rev-parse' '--show-toplevel') - STAGE="$ROOT"'/.release-bins-staging' - 'mkdir' '-p' "$STAGE" - '[' '-s' "$ROOT"'/gunbc-floor-cmd.log' ']' || ('echo' '::error title=release-bins BuildDiagnosticsMissing::the product requires the build log gunbc-floor-cmd.log as a pack member and the build step left none; a pack without its diagnostics is incomplete, not smaller'; exit 1) - 'cp' "$ROOT"'/gunbc-floor-cmd.log' "$ROOT"'/target/release/build_diagnostics' - ('cd' "$ROOT"'/target/release' && 'sha256sum' 'claim_executor' 'gunbc' 'build_diagnostics') > "$STAGE"'/release-bins.manifest' - 'tar' '-czf' "$STAGE"'/release-bins.tgz' '-C' "$ROOT"'/target/release' 'claim_executor' 'gunbc' 'build_diagnostics' - 'cp' "$RUNNER_TEMP"'/release-bins.preimage.expected' "$STAGE"'/release-bins.preimage' - 'sha256sum' "$STAGE"'/release-bins.manifest' > "$RUNNER_TEMP"'/OUTPUT_DIGEST.out' - OUTPUT_DIGEST=$('cut' '-d' ' ' '-f' '1' "$RUNNER_TEMP"'/OUTPUT_DIGEST.out') - 'echo' 'ReleaseBinsBuildReceipt: request='"$RELEASE_BINS_KEY"' output=sha256:'"$OUTPUT_DIGEST"' dispatch='"$GITHUB_RUN_ID"'/'"$GITHUB_RUN_ATTEMPT"' built=yes publish=requested-not-yet-persisted scope='"$GITHUB_REF" - - set -e - ROOT=$('git' 'rev-parse' '--show-toplevel') - STAGE="$ROOT"'/.release-bins-staging' - 'tar' '-cf' "$RUNNER_TEMP"'/pack.tar' '-C' "$STAGE" 'release-bins.preimage' 'release-bins.manifest' 'release-bins.tgz' - if: github.event_name == 'merge_group' && steps.build_witness_fold.outcome == 'success' - - name: Upload the compiler pair candidate - uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f - with: - name: compiler-pair-candidate - path: ${{ runner.temp }}/pack.tar - if-no-files-found: error - retention-days: 1 - compression-level: 0 - if: github.event_name == 'merge_group' && steps.build_witness_fold.outcome == 'success' + 'grep' '-qxF' 'source_closure='"$SOURCE_CLOSURE" "$RUNNER_TEMP"'/release-bins-download/release-bins.preimage' || ('echo' '::error title=release-bins ConsumerSourceTreeMismatch::the pack was keyed on a different source tree than this job'\''s checkout'; exit 1) + 'printf' '%s' 'claim_executor + gunbc + build_diagnostics + claim_executor_checker_inputs + ' > "$RUNNER_TEMP"'/release-bins.roster' + 'tar' '-tzf' "$RUNNER_TEMP"'/release-bins-download/release-bins.tgz' > "$RUNNER_TEMP"'/pack-listing.out' + 'cmp' '-s' "$RUNNER_TEMP"'/pack-listing.out' "$RUNNER_TEMP"'/release-bins.roster' || ('echo' '::error title=release-bins PackListingNotRoster::the downloaded pack'\''s archive listing is not exactly the roster (an extra, duplicate, directory or escaping entry)'; exit 1) + 'cut' '-c' '67-' "$RUNNER_TEMP"'/release-bins-download/release-bins.manifest' > "$RUNNER_TEMP"'/manifest-names.out' + 'cmp' '-s' "$RUNNER_TEMP"'/manifest-names.out' "$RUNNER_TEMP"'/release-bins.roster' || ('echo' '::error title=release-bins ManifestRosterMismatch::the downloaded manifest does not name exactly the gunbc.ci_release_bins roster in order'; exit 1) + if 'grep' '-qvE' '^[0-9a-f]{64} [A-Za-z0-9_]+$' "$RUNNER_TEMP"'/release-bins-download/release-bins.manifest'; then ('echo' '::error title=release-bins ManifestMalformed::the downloaded manifest has a line that is not one well-formed sha256 record for a roster member'; exit 1); fi + 'rm' '-rf' "$RUNNER_TEMP"'/release-bins-check' + 'mkdir' '-p' "$RUNNER_TEMP"'/release-bins-check' + 'tar' '-xzf' "$RUNNER_TEMP"'/release-bins-download/release-bins.tgz' '-C' "$RUNNER_TEMP"'/release-bins-check' '--no-same-owner' + 'printf' '%s' 'claim_executor:regular file:1 + gunbc:regular file:1 + ' > "$RUNNER_TEMP"'/member-types.expected' + ('cd' "$RUNNER_TEMP"'/release-bins-check' && 'stat' '-c' '%n:%F:%h' 'claim_executor' 'gunbc') > "$RUNNER_TEMP"'/member-types.out' + 'cmp' '-s' "$RUNNER_TEMP"'/member-types.out' "$RUNNER_TEMP"'/member-types.expected' || ('diff' "$RUNNER_TEMP"'/member-types.expected' "$RUNNER_TEMP"'/member-types.out' || ':' 'no-op'; ('echo' '::error title=release-bins MemberNotRegularFile::the downloaded pack has a member that is not a self-contained regular file (a link, special file or hard-link dependency; the report above names it)'; exit 1)) + ('cd' "$RUNNER_TEMP"'/release-bins-check' && 'find' 'claim_executor' 'gunbc' '-maxdepth' '0' '!' '-perm' '-u+x') > "$RUNNER_TEMP"'/member-nonexec.out' + '[' '!' '-s' "$RUNNER_TEMP"'/member-nonexec.out' ']' || ('cat' "$RUNNER_TEMP"'/member-nonexec.out'; ('echo' '::error title=release-bins MemberNotExecutable::the downloaded pack has a member that is not executable (listed above)'; exit 1)) + ('cd' "$RUNNER_TEMP"'/release-bins-check' && 'sha256sum' '--strict' '--quiet' '-c' "$RUNNER_TEMP"'/release-bins-download/release-bins.manifest') || ('echo' '::error title=release-bins MemberDigestMismatch::the downloaded member bytes do not match the manifest under strict checking; refusing before any member is promoted or run'; exit 1) + 'mkdir' '-p' "$ROOT"'/target/release' + ('cd' "$ROOT"'/target/release' && 'rm' '-f' 'claim_executor' 'gunbc') + ('cd' "$RUNNER_TEMP"'/release-bins-check' && 'cp' '-p' 'claim_executor' 'gunbc' "$ROOT"'/target/release/') + ('cd' "$RUNNER_TEMP"'/release-bins-check' && 'cp' '-p' 'claim_executor_checker_inputs' "$ROOT"'/target/release/') + 'rm' '-rf' "$RUNNER_TEMP"'/release-bins-check' + "$ROOT"'/target/release/claim_executor' '--verify-build-artifacts' "$ROOT"'/target/release/claim_executor' "$ROOT"'/target/release/gunbc' + env: + RELEASE_BINS_KEY: ${{ needs.seed.outputs.release_bins_key }} + timeout-minutes: 5 - name: Require isolated toolchain homes run: | if [ -z "${CARGO_HOME:-}" ] || [ -z "${RUSTUP_HOME:-}" ]; then echo "::error::ToolchainHomesNotIsolated CARGO_HOME=${CARGO_HOME:-unset} RUSTUP_HOME=${RUSTUP_HOME:-unset} -- the isolation step did not run, so this job shares a toolchain with every other runner slot on this host and a concurrent install can replace a binary mid-run" >&2; exit 1; fi @@ -479,6 +695,7 @@ jobs: GUNBC_REQUIRED_FLOOR_CROSS_CLAIM_DEMAND: required_floor_cross_claim_demand.tsv generated: runs-on: [self-hosted, linux, arm64] + needs: [seed] timeout-minutes: 90 if: github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == github.repository permissions: @@ -510,30 +727,60 @@ jobs: if [ -x "$CARGO_HOME/bin/cargo" ]; then CARGO_BIN="$CARGO_HOME/bin/cargo"; else CARGO_BIN="$(command -v cargo || true)"; fi if [ -z "$CARGO_BIN" ]; then echo "::error::no cargo binary: neither the isolated $CARGO_HOME/bin/cargo shim nor PATH carries one"; exit 1; fi echo "CARGO_BIN=$CARGO_BIN" >> "$GITHUB_ENV" - - name: Build the compiler and the witness executor + - name: Download release-bins artifact + uses: actions/download-artifact@37930b1c2abaa49bbe596cd826c3c89aef350131 + with: + name: release-bins + path: ${{ runner.temp }}/release-bins-download + timeout-minutes: 10 + - name: Verify the seed's compiler pair against its key and promote it into this checkout id: build_witness_fold - run: |+ - GUNBC_FLOOR_LOG='gunbc-floor-cmd.log' - 'set' '+e' - 'set' '-o' 'pipefail' - ('sh' '-e' '-c' 'cargo build --release -p v1-compiler --bin claim_executor --bin gunbc') 2>&1 | 'tee' "$GUNBC_FLOOR_LOG" - GUNBC_FLOOR_EXIT="$?" - GUNBC_FLOOR_RECEIPT='gunbc-floor-outcome.txt' - GUNBC_FLOOR_CLASS='structural' - GUNBC_FLOOR_SIGNATURE='' - if '[' "$GUNBC_FLOOR_EXIT" '-eq' '126' ']'; then GUNBC_FLOOR_CLASS='infra'; GUNBC_FLOOR_SIGNATURE='CommandInvokedCannotExecute'; fi - if '[' "$GUNBC_FLOOR_CLASS" '=' 'structural' ']' && '[' "$GUNBC_FLOOR_EXIT" '-eq' '127' ']'; then GUNBC_FLOOR_CLASS='infra'; GUNBC_FLOOR_SIGNATURE='CommandNotFound'; fi - if '[' "$GUNBC_FLOOR_CLASS" '=' 'structural' ']' && '[' "$GUNBC_FLOOR_EXIT" '-eq' '0' ']'; then GUNBC_FLOOR_CLASS='none'; GUNBC_FLOOR_SIGNATURE=''; fi - if '[' "$GUNBC_FLOOR_CLASS" '=' 'structural' ']' && '[' '-f' "$GUNBC_FLOOR_LOG" ']' && 'grep' '-q' 'MemoryStallRefusedPageThrash' "$GUNBC_FLOOR_LOG"; then GUNBC_FLOOR_CLASS='infra'; GUNBC_FLOOR_SIGNATURE='MemoryStallRefusedPageThrash'; fi - if '[' "$GUNBC_FLOOR_CLASS" '=' 'structural' ']' && '[' '-f' "$GUNBC_FLOOR_LOG" ']' && 'grep' '-q' 'The runner has received a shutdown signal' "$GUNBC_FLOOR_LOG"; then GUNBC_FLOOR_CLASS='infra'; GUNBC_FLOOR_SIGNATURE='RunnerLost'; fi - if '[' "$GUNBC_FLOOR_CLASS" '=' 'structural' ']' && '[' '-f' "$GUNBC_FLOOR_LOG" ']' && 'grep' '-q' 'sccache: error: failed to execute compile' "$GUNBC_FLOOR_LOG"; then GUNBC_FLOOR_CLASS='infra'; GUNBC_FLOOR_SIGNATURE='SccacheFailedToExecuteCompile'; fi - if '[' "$GUNBC_FLOOR_CLASS" '=' 'structural' ']' && '[' '-f' "$GUNBC_FLOOR_LOG" ']' && 'grep' '-q' '(exit status: 254)' "$GUNBC_FLOOR_LOG"; then GUNBC_FLOOR_CLASS='infra'; GUNBC_FLOOR_SIGNATURE='SccacheRustcWrapperExit254'; fi - if '[' "$GUNBC_FLOOR_CLASS" '=' 'structural' ']' && '[' '-f' "$GUNBC_FLOOR_LOG" ']' && 'grep' '-q' 'sccache: encountered fatal error' "$GUNBC_FLOOR_LOG"; then GUNBC_FLOOR_CLASS='infra'; GUNBC_FLOOR_SIGNATURE='SccacheFatalError'; fi - if '[' "$GUNBC_FLOOR_CLASS" '=' 'structural' ']' && '[' '-f' "$GUNBC_FLOOR_LOG" ']' && 'grep' '-q' 'failed to spawn' "$GUNBC_FLOOR_LOG"; then GUNBC_FLOOR_CLASS='infra'; GUNBC_FLOOR_SIGNATURE='ProcessSpawnFailure'; fi - if '[' "$GUNBC_FLOOR_CLASS" '=' 'structural' ']' && '[' '-f' "$GUNBC_FLOOR_LOG" ']' && 'grep' '-q' 'Resource temporarily unavailable' "$GUNBC_FLOOR_LOG"; then GUNBC_FLOOR_CLASS='infra'; GUNBC_FLOOR_SIGNATURE='ResourceTemporarilyUnavailable'; fi - if (! '[' '-f' "$GUNBC_FLOOR_RECEIPT" ']') || '[' "$GUNBC_FLOOR_CLASS" '=' 'structural' ']' || ('[' "$GUNBC_FLOOR_CLASS" '=' 'infra' ']' && (! ('[' '-f' "$GUNBC_FLOOR_RECEIPT" ']' && 'grep' '-q' 'class=structural' "$GUNBC_FLOOR_RECEIPT"))) || ('[' "$GUNBC_FLOOR_CLASS" '=' 'none' ']' && (! ('[' '-f' "$GUNBC_FLOOR_RECEIPT" ']' && 'grep' '-q' 'class=structural' "$GUNBC_FLOOR_RECEIPT")) && (! ('[' '-f' "$GUNBC_FLOOR_RECEIPT" ']' && 'grep' '-q' 'class=infra' "$GUNBC_FLOOR_RECEIPT"))); then 'printf' 'class=%s\nsignature=%s\nexit=%s\n' "$GUNBC_FLOOR_CLASS" "$GUNBC_FLOOR_SIGNATURE" "$GUNBC_FLOOR_EXIT" > "$GUNBC_FLOOR_RECEIPT"; if '[' "$GUNBC_FLOOR_CLASS" '=' 'infra' ']'; then 'echo' '::error title=environment::floor_class='"$GUNBC_FLOOR_CLASS"' signature='"$GUNBC_FLOOR_SIGNATURE"' exit='"$GUNBC_FLOOR_EXIT"'; this is not a verdict about the diff. Attempt receipt: '"$GUNBC_FLOOR_RECEIPT"; fi; if '[' "$GUNBC_FLOOR_CLASS" '=' 'structural' ']'; then 'echo' '::error title=subject::floor_class='"$GUNBC_FLOOR_CLASS"' exit='"$GUNBC_FLOOR_EXIT"'; read the step log for the subject defect'; fi; fi - 'exit' "$GUNBC_FLOOR_EXIT" - + run: | + set -e + ROOT=$('git' 'rev-parse' '--show-toplevel') + '[' '-n' "$RELEASE_BINS_KEY" ']' || ('echo' '::error title=release-bins ConsumerKeyMissing::the build job published no release-bins key'; exit 1) + '[' '-f' "$RUNNER_TEMP"'/release-bins-download/release-bins.preimage' ']' || ('echo' '::error title=release-bins ConsumerFileMissing::the release-bins artifact is missing release-bins.preimage'; exit 1) + '[' '-f' "$RUNNER_TEMP"'/release-bins-download/release-bins.manifest' ']' || ('echo' '::error title=release-bins ConsumerFileMissing::the release-bins artifact is missing release-bins.manifest'; exit 1) + '[' '-f' "$RUNNER_TEMP"'/release-bins-download/release-bins.tgz' ']' || ('echo' '::error title=release-bins ConsumerFileMissing::the release-bins artifact is missing release-bins.tgz'; exit 1) + 'sha256sum' "$RUNNER_TEMP"'/release-bins-download/release-bins.preimage' > "$RUNNER_TEMP"'/DOWNLOADED_DIGEST.out' + DOWNLOADED_DIGEST=$('cut' '-d' ' ' '-f' '1' "$RUNNER_TEMP"'/DOWNLOADED_DIGEST.out') + '[' 'release-bins-'"$DOWNLOADED_DIGEST" '=' "$RELEASE_BINS_KEY" ']' || ('echo' '::error title=release-bins ConsumerPreimageKeyMismatch::the downloaded preimage does not hash to the build job'\''s key'; exit 1) + OBJECT_FORMAT=$('git' '-C' "$ROOT" 'rev-parse' '--show-object-format') + SOURCE_TREE=$('git' '-C' "$ROOT" 'rev-parse' 'HEAD^{tree}') + '[' '-n' "$OBJECT_FORMAT" ']' || ('echo' '::error title=release-bins AxisValueUnread::the key input OBJECT_FORMAT read back empty, so the key would not name the build; refusing to key'; exit 1) + '[' '-n' "$SOURCE_TREE" ']' || ('echo' '::error title=release-bins AxisValueUnread::the key input SOURCE_TREE read back empty, so the key would not name the build; refusing to key'; exit 1) + SOURCE_CLOSURE='tree:'"$OBJECT_FORMAT"':'"$SOURCE_TREE" + 'grep' '-qxF' 'source_closure='"$SOURCE_CLOSURE" "$RUNNER_TEMP"'/release-bins-download/release-bins.preimage' || ('echo' '::error title=release-bins ConsumerSourceTreeMismatch::the pack was keyed on a different source tree than this job'\''s checkout'; exit 1) + 'printf' '%s' 'claim_executor + gunbc + build_diagnostics + claim_executor_checker_inputs + ' > "$RUNNER_TEMP"'/release-bins.roster' + 'tar' '-tzf' "$RUNNER_TEMP"'/release-bins-download/release-bins.tgz' > "$RUNNER_TEMP"'/pack-listing.out' + 'cmp' '-s' "$RUNNER_TEMP"'/pack-listing.out' "$RUNNER_TEMP"'/release-bins.roster' || ('echo' '::error title=release-bins PackListingNotRoster::the downloaded pack'\''s archive listing is not exactly the roster (an extra, duplicate, directory or escaping entry)'; exit 1) + 'cut' '-c' '67-' "$RUNNER_TEMP"'/release-bins-download/release-bins.manifest' > "$RUNNER_TEMP"'/manifest-names.out' + 'cmp' '-s' "$RUNNER_TEMP"'/manifest-names.out' "$RUNNER_TEMP"'/release-bins.roster' || ('echo' '::error title=release-bins ManifestRosterMismatch::the downloaded manifest does not name exactly the gunbc.ci_release_bins roster in order'; exit 1) + if 'grep' '-qvE' '^[0-9a-f]{64} [A-Za-z0-9_]+$' "$RUNNER_TEMP"'/release-bins-download/release-bins.manifest'; then ('echo' '::error title=release-bins ManifestMalformed::the downloaded manifest has a line that is not one well-formed sha256 record for a roster member'; exit 1); fi + 'rm' '-rf' "$RUNNER_TEMP"'/release-bins-check' + 'mkdir' '-p' "$RUNNER_TEMP"'/release-bins-check' + 'tar' '-xzf' "$RUNNER_TEMP"'/release-bins-download/release-bins.tgz' '-C' "$RUNNER_TEMP"'/release-bins-check' '--no-same-owner' + 'printf' '%s' 'claim_executor:regular file:1 + gunbc:regular file:1 + ' > "$RUNNER_TEMP"'/member-types.expected' + ('cd' "$RUNNER_TEMP"'/release-bins-check' && 'stat' '-c' '%n:%F:%h' 'claim_executor' 'gunbc') > "$RUNNER_TEMP"'/member-types.out' + 'cmp' '-s' "$RUNNER_TEMP"'/member-types.out' "$RUNNER_TEMP"'/member-types.expected' || ('diff' "$RUNNER_TEMP"'/member-types.expected' "$RUNNER_TEMP"'/member-types.out' || ':' 'no-op'; ('echo' '::error title=release-bins MemberNotRegularFile::the downloaded pack has a member that is not a self-contained regular file (a link, special file or hard-link dependency; the report above names it)'; exit 1)) + ('cd' "$RUNNER_TEMP"'/release-bins-check' && 'find' 'claim_executor' 'gunbc' '-maxdepth' '0' '!' '-perm' '-u+x') > "$RUNNER_TEMP"'/member-nonexec.out' + '[' '!' '-s' "$RUNNER_TEMP"'/member-nonexec.out' ']' || ('cat' "$RUNNER_TEMP"'/member-nonexec.out'; ('echo' '::error title=release-bins MemberNotExecutable::the downloaded pack has a member that is not executable (listed above)'; exit 1)) + ('cd' "$RUNNER_TEMP"'/release-bins-check' && 'sha256sum' '--strict' '--quiet' '-c' "$RUNNER_TEMP"'/release-bins-download/release-bins.manifest') || ('echo' '::error title=release-bins MemberDigestMismatch::the downloaded member bytes do not match the manifest under strict checking; refusing before any member is promoted or run'; exit 1) + 'mkdir' '-p' "$ROOT"'/target/release' + ('cd' "$ROOT"'/target/release' && 'rm' '-f' 'claim_executor' 'gunbc') + ('cd' "$RUNNER_TEMP"'/release-bins-check' && 'cp' '-p' 'claim_executor' 'gunbc' "$ROOT"'/target/release/') + ('cd' "$RUNNER_TEMP"'/release-bins-check' && 'cp' '-p' 'claim_executor_checker_inputs' "$ROOT"'/target/release/') + 'rm' '-rf' "$RUNNER_TEMP"'/release-bins-check' + "$ROOT"'/target/release/claim_executor' '--verify-build-artifacts' "$ROOT"'/target/release/claim_executor' "$ROOT"'/target/release/gunbc' + env: + RELEASE_BINS_KEY: ${{ needs.seed.outputs.release_bins_key }} + timeout-minutes: 5 - name: Require isolated toolchain homes run: | if [ -z "${CARGO_HOME:-}" ] || [ -z "${RUSTUP_HOME:-}" ]; then echo "::error::ToolchainHomesNotIsolated CARGO_HOME=${CARGO_HOME:-unset} RUSTUP_HOME=${RUSTUP_HOME:-unset} -- the isolation step did not run, so this job shares a toolchain with every other runner slot on this host and a concurrent install can replace a binary mid-run" >&2; exit 1; fi @@ -633,7 +880,7 @@ jobs: MALLOC_ARENA_MAX: "2" witnesses: runs-on: [self-hosted, linux, arm64] - needs: [rust-unit-tests, emit-build, floor, generated] + needs: [rust-unit-tests, seed, emit-build, floor, generated] timeout-minutes: 5 if: always() permissions: @@ -641,14 +888,16 @@ jobs: steps: - name: Every required lane must have succeeded run: |- - echo "required lanes: rust-unit-tests=$RUST_UNIT_TESTS emit-build=$EMIT_BUILD floor=$FLOOR generated=$GENERATED (same_repo=$SAME_REPO)" - if [ "$RUST_UNIT_TESTS" = failure ] || [ "$EMIT_BUILD" = failure ] || [ "$FLOOR" = failure ] || [ "$GENERATED" = failure ]; then echo "::error::a required lane concluded failure (rust-unit-tests=$RUST_UNIT_TESTS emit-build=$EMIT_BUILD floor=$FLOOR generated=$GENERATED) - open that job's log" >&2; exit 1; fi + echo "required lanes: rust-unit-tests=$RUST_UNIT_TESTS seed=$SEED emit-build=$EMIT_BUILD floor=$FLOOR generated=$GENERATED (same_repo=$SAME_REPO)" + if [ "$RUST_UNIT_TESTS" = failure ] || [ "$SEED" = failure ] || [ "$EMIT_BUILD" = failure ] || [ "$FLOOR" = failure ] || [ "$GENERATED" = failure ]; then echo "::error::a required lane concluded failure (rust-unit-tests=$RUST_UNIT_TESTS seed=$SEED emit-build=$EMIT_BUILD floor=$FLOOR generated=$GENERATED) - open that job's log" >&2; exit 1; fi if [ "$RUST_UNIT_TESTS" != success ]; then if [ "$GITHUB_EVENT_NAME" = pull_request ] && [ "$RUST_UNIT_TESTS" = skipped ]; then echo "::notice::the v1 unit-test lane does not run on pull_request: it runs on merge_group and workflow_dispatch on the fleet"; else echo "::error::the v1 unit-test lane produced no success (rust-unit-tests=$RUST_UNIT_TESTS), so this head carries no verdict from that lane"; exit 1; fi; fi + if [ "$SEED" != success ]; then if [ "$SAME_REPO" = false ] && [ "$SEED" = skipped ]; then echo "::notice::the seed lane is skipped for a fork pull request: no compiler pair was built or restored for this head"; else echo "::error::the seed lane produced no success (seed=$SEED), so this head carries no verdict from that lane"; exit 1; fi; fi if [ "$EMIT_BUILD" != success ]; then if [ "$SAME_REPO" = false ] && [ "$EMIT_BUILD" = skipped ]; then echo "::notice::the emit-build lane is skipped for a fork pull request: no self-host or native-CLI emission verdict for this head"; else echo "::error::the emit-build lane produced no success (emit-build=$EMIT_BUILD), so this head carries no verdict from that lane"; exit 1; fi; fi if [ "$FLOOR" != success ]; then if [ "$SAME_REPO" = false ] && [ "$FLOOR" = skipped ]; then echo "::notice::the fleet lane is skipped for a fork pull request: no witnesses fold and no generated-artifact verdict for this head"; else echo "::error::the fleet lane produced no success (floor=$FLOOR), so this head carries no verdict from that lane"; exit 1; fi; fi if [ "$GENERATED" != success ]; then if [ "$SAME_REPO" = false ] && [ "$GENERATED" = skipped ]; then echo "::notice::the generated-artifact lane is skipped for a fork pull request: no generated-artifact verdict for this head"; else echo "::error::the generated-artifact lane produced no success (generated=$GENERATED), so this head carries no verdict from that lane"; exit 1; fi; fi env: RUST_UNIT_TESTS: ${{ needs['rust-unit-tests'].result }} + SEED: ${{ needs.seed.result }} EMIT_BUILD: ${{ needs['emit-build'].result }} FLOOR: ${{ needs.floor.result }} GENERATED: ${{ needs.generated.result }} diff --git a/dag/gunbc/fleet/fleet_release_bins_key.dag b/dag/gunbc/fleet/fleet_release_bins_key.dag index 02d45e4aa8f..a85e69d4457 100644 --- a/dag/gunbc/fleet/fleet_release_bins_key.dag +++ b/dag/gunbc/fleet/fleet_release_bins_key.dag @@ -161,6 +161,8 @@ type ReleaseBinsProduct { roster: List build_line: String diagnostics: ReleaseBinsDiagnostics + sidecars: List + sidecar_writer: List } // WHAT THE BUILD SAID, CARRIED AS A PACK MEMBER. A build whose warnings were dropped is not the build @@ -173,16 +175,27 @@ type ReleaseBinsDiagnostics = NoBuildDiagnostics | BuildLogMember { member: String, source: String } +// A SIDECAR IS A DATA MEMBER THE PRODUCING JOB WROTE INTO target/release BESIDE THE BINARIES, named by +// product and carried by the pack under the same digest, listing and verification as every member +// (it is not executable, so it is not in the roster the executable checks run over). A consumer +// promotes it only after the whole pack verified -- including ConsumerSourceTreeMismatch -- so its +// presence beside a restored binary is evidence the pack was keyed on this tree. The compiler pair +// uses one to carry the checker's input record (claim_executor --write-checker-inputs), the form of +// cargo's dep-info that survives a change of checkout, so the floor's checker-subject rule can run +// against a restored binary without a rebased copy of the old evidence. fn release_bins_pack_members(product: ReleaseBinsProduct) -> List { - match product.diagnostics { - NoBuildDiagnostics => product.roster - BuildLogMember { member: m, source: _ } => concat(product.roster, [m]) - } + concat( + match product.diagnostics { + NoBuildDiagnostics => product.roster + BuildLogMember { member: m, source: _ } => concat(product.roster, [m]) + }, + product.sidecars, + ) } // THE FLEET PACK: every enrolled release binary, built by fleet-converge's build job. fn fleet_release_bins_product() -> ReleaseBinsProduct { - ReleaseBinsProduct { kind: release_bins_key_kind, roster: ci_floor_required_artifact_names(), build_line: ci_release_build_line(), diagnostics: NoBuildDiagnostics } + ReleaseBinsProduct { kind: release_bins_key_kind, roster: ci_floor_required_artifact_names(), build_line: ci_release_build_line(), diagnostics: NoBuildDiagnostics, sidecars: [], sidecar_writer: [] } } // THE COMPILER PAIR: the two binaries the required floor builds anyway (gunbc.ci_release_bins @@ -191,12 +204,18 @@ fn fleet_release_bins_product() -> ReleaseBinsProduct { // consumes, so the key names the build the floor ran. It is the product the merge queue produces // as a candidate, fleet-desired publishes, and hw boot consumes -- none of which needs the other // fourteen binaries. +// claim_executor's own spelling of the sidecar beside it: _checker_inputs +// (v1_compiler cli_run checker_dependency restored_checker_inputs_path). +data compiler_pair_checker_inputs_sidecar: String = "claim_executor_checker_inputs" + fn compiler_pair_release_product() -> ReleaseBinsProduct { ReleaseBinsProduct { kind: "gunbc.compiler_pair_release_bins", roster: ci_floor_invoked_bins, build_line: repo_self_build_command(bins: ci_floor_invoked_bins), diagnostics: BuildLogMember { member: "build_diagnostics", source: floor_attempt_command_log_filename }, + sidecars: [compiler_pair_checker_inputs_sidecar], + sidecar_writer: ["./target/release/claim_executor", "--write-checker-inputs"], } } data release_bins_admitted_producer_ref: String = "refs/heads/main" @@ -316,6 +335,7 @@ type ReleaseBinsRefusal | ConsumerPreimageKeyMismatch | ConsumerSourceTreeMismatch | BuildDiagnosticsMissing { file: String } + | SidecarMissing { file: String } | ConsumerProductKindMismatch { kind: String } fn release_bins_refusal_name(refusal: ReleaseBinsRefusal) -> String { @@ -346,6 +366,7 @@ fn release_bins_refusal_name(refusal: ReleaseBinsRefusal) -> String { ConsumerPreimageKeyMismatch => "ConsumerPreimageKeyMismatch" ConsumerSourceTreeMismatch => "ConsumerSourceTreeMismatch" BuildDiagnosticsMissing { file: _ } => "BuildDiagnosticsMissing" + SidecarMissing { file: _ } => "SidecarMissing" ConsumerProductKindMismatch { kind: _ } => "ConsumerProductKindMismatch" } } @@ -378,6 +399,7 @@ fn release_bins_refusal_message(refusal: ReleaseBinsRefusal) -> String { ConsumerPreimageKeyMismatch => "the downloaded preimage does not hash to the build job's key" ConsumerSourceTreeMismatch => "the pack was keyed on a different source tree than this job's checkout" ConsumerProductKindMismatch { kind: k } => join(["the pack's preimage does not name the product kind ", k, " this consumer asked for; it is another product's pack"], "") + SidecarMissing { file: f } => join(["the product requires the sidecar ", f, " as a pack member and the producing job left none; a pack without it is incomplete, not smaller"], "") BuildDiagnosticsMissing { file: f } => join(["the product requires the build log ", f, " as a pack member and the build step left none; a pack without its diagnostics is incomplete, not smaller"], "") } } @@ -801,11 +823,11 @@ fn release_bins_member_checks(site: ReleaseBinsVerifySite, dir: Node, product: R } fn release_bins_promote_all(dir: Node, product: ReleaseBinsProduct) -> List { - [ + concat([ cmd(ws: [lit(t: "mkdir"), lit(t: "-p"), rooted(file: "target/release")]), in_dir(dir: rooted(file: "target/release"), command: cmd(ws: concat([lit(t: "rm"), lit(t: "-f")], release_bins_roster_words(product: product)))), in_dir(dir: dir, command: cmd(ws: concat(concat([lit(t: "cp"), lit(t: "-p")], release_bins_roster_words(product: product)), [rooted(file: "target/release/")]))), - ] + ], map(product.sidecars, f => in_dir(dir: dir, command: cmd(ws: [lit(t: "cp"), lit(t: "-p"), lit(t: f), rooted(file: "target/release/")])))) } fn release_bins_verify_pack(site: ReleaseBinsVerifySite, archive: Node, manifest: Node, dir: Node, product: ReleaseBinsProduct) -> List { @@ -1108,6 +1130,7 @@ fn release_bins_pack_statements(product: ReleaseBinsProduct) -> List { cmd(ws: [lit(t: "mkdir"), lit(t: "-p"), var(n: "STAGE")]), ], release_bins_diagnostics_capture(product: product), + release_bins_sidecar_capture(product: product), [ bash_build_with_redir_to_file( inner: bash_build_subshell_from_stmts(stmts: [bash_build_and_then( @@ -1139,6 +1162,25 @@ fn release_bins_diagnostics_capture(product: ReleaseBinsProduct) -> List { } } +// THE SIDECARS ARE ALREADY IN target/release (the producing job wrote them there), so capture only +// requires each to exist non-empty: a product that declares a sidecar and finds none refuses. +fn release_bins_sidecar_capture(product: ReleaseBinsProduct) -> List { + concat(release_bins_sidecar_writes(product: product), map(product.sidecars, f => or_refuse( + check: test_words(ws: [lit(t: "-s"), rooted(file: join(["target/release/", f], ""))]), + r: SidecarMissing { file: f }, + ))) +} + +// THE WRITER RUNS IN THE SHARED PACK PATH, so every producer of the product (the witnesses seed job +// and fleet-converge's CompilerPairOnly modes alike) emits the sidecar from the binary it just built; +// a product whose writer is empty declares sidecars it expects the build itself to have left. +fn release_bins_sidecar_writes(product: ReleaseBinsProduct) -> List { + match length(xs: product.sidecar_writer) == 0 { + true => [] + false => map(product.sidecars, f => cmd(ws: concat(map(product.sidecar_writer, w => lit(t: w)), [rooted(file: join(["target/release/", f], ""))]))) + } +} + // CONSUMER: a downstream job holds the key its build job published and refuses, before executing // anything it downloaded, a pack whose preimage hashes elsewhere, names another source tree, or whose // members do not match the manifest. diff --git a/dag/gunbc/witness/compiler_gate_workflow.dag b/dag/gunbc/witness/compiler_gate_workflow.dag index f2922e342ca..24964b7579b 100644 --- a/dag/gunbc/witness/compiler_gate_workflow.dag +++ b/dag/gunbc/witness/compiler_gate_workflow.dag @@ -2,7 +2,7 @@ module gunbc.compiler_gate_workflow // NO LANE THE REQUIRED CONTEXT READS RUNS ON A HOSTED RUNNER (operator ruling 2026-09-28). The -// lanes the `witnesses` aggregate reads -- `rust-unit-tests`, `emit-build`, `floor` and `generated` -- +// lanes the `witnesses` aggregate reads -- `seed`, `rust-unit-tests`, `emit-build`, `floor` and `generated` -- // run on the fleet, and so does the aggregate. // // THE HOSTED UNIT-TEST LANE WAS DELETED (operator ruling 2026-09-29). `rust-unit-tests` ran @@ -21,8 +21,8 @@ module gunbc.compiler_gate_workflow // jobs API's created_at and started_at rather than trusted here: merge-queue runs 36478070174 and // 36464765845, where every hosted job waited minutes for a slot -- the aggregate, whose one step // runs in seconds, waited 9 and 21 -- while the fleet lanes of the same runs started within -// seconds. So `compiler` is deleted: its one step built `gunbc`, which the fleet prelude of both -// `floor` and `generated` already builds from the same tree (DESIGN section 2). The lint became a +// seconds. So `compiler` is deleted: its one step built `gunbc`, which the `seed` job now builds or restores once +// for `floor` and `generated` (`emit-build` still builds its own, see the seed section) (DESIGN section 2). The lint became a // step on `generated` (compiler_gate_lint_step), and the aggregate moved to the fleet. // compiler_gate_merge_path_runs_on_the_fleet refuses emission of a workflow in which a blocking // lane or the aggregate runs hosted. @@ -144,7 +144,15 @@ import gunbc.heal_candidate { import gunbc.heal_publication_boundary { heal_repair_candidate_artifact_name } // The fork clauses are heal's authorship; the fleet lane inherited the work they guarded. import gunbc.heal_workflow { heal_same_repository_clause, heal_not_dependabot_clause } +import gunbc.fleet_workflow_steps { + ci_cache_cargo_step, ci_release_bins_lookup_step, ci_release_bins_cache_step, + ci_release_bins_upload_step, ci_release_bins_download_step, gunbc_ci_aux_step_timeout_minutes, +} import gunbc.fleet_release_bins_key { + release_bins_outcome_script_for, release_bins_consumer_key_check_script_for, + release_bins_key_expression, release_bins_key_step_id, release_bins_outcome_step_id, + release_bins_lookup_standing_expression, release_bins_lookup_entry_expression, release_bins_cache_hit_expression, + release_bins_key_job_output, release_bins_build_condition, compiler_pair_release_product, release_bins_key_script_for, release_bins_pack_manifest_script_for, release_bins_pack_object_script, release_bins_pack_object_path_expression, compiler_pair_candidate_artifact_name, compiler_pair_candidate_retention_whole_days, @@ -757,12 +765,7 @@ fn compiler_gate_candidate_upload_step() -> Step { } fn compiler_gate_floor_bound_steps() -> List { - concat(compiler_gate_fleet_prelude_bound_steps_around_build( - bins: witness_floor_required_bins, - build_step_name: compiler_gate_floor_build_step_name, - before_build: compiler_gate_candidate_key_bound_steps(), - after_build: compiler_gate_candidate_pack_bound_steps(), - ), concat([ + concat(compiler_gate_fleet_prelude_bound_steps(), concat([ CompilerGateBoundStep { step: compiler_gate_floor_run_step(), role: consumes_only(capabilities: [RustfmtCapability]), @@ -771,24 +774,9 @@ fn compiler_gate_floor_bound_steps() -> List { ], compiler_gate_floor_measurement_bound_steps())) } -// THE PRELUDE BOTH FLEET JOBS NEED, ONE LIST RATHER THAN TWO COPIES: the head checkout, isolated -// toolchain homes, a pinned default, and claim_executor plus gunbc built from the judged tree. The -// build step keeps its id, build_witness_fold, in both jobs because every downstream condition in -// each job reads that id; step ids are per job, so the two never collide. -fn compiler_gate_fleet_prelude_bound_steps() -> List { - compiler_gate_fleet_prelude_bound_steps_around_build( - bins: witness_floor_required_bins, - build_step_name: compiler_gate_floor_build_step_name, - before_build: [], - after_build: [], - ) -} - -// emit-build needs only `gunbc` (its instruments run through `gunbc test`), so it takes the same -// prelude over its own binaries rather than a copy of it; the floor adds its candidate-key and -// candidate-pack steps around the same build. -fn compiler_gate_fleet_prelude_bound_steps_around_build(bins: List, build_step_name: String, before_build: List, after_build: List) -> List { - concat([ +// THE HEAD OF EVERY FLEET JOB: the judged checkout, an isolated toolchain home and a pinned default. +fn compiler_gate_fleet_head_bound_steps() -> List { + [ CompilerGateBoundStep { step: compiler_gate_floor_checkout_step(), role: capability_neutral, @@ -809,22 +797,33 @@ fn compiler_gate_fleet_prelude_bound_steps_around_build(bins: List, buil role: consumes_only(capabilities: [RustupCapability, CargoCapability]), step_name: "Pin rustup default (isolated RUSTUP_HOME has no default toolchain)", }, - ], concat(before_build, concat([ + ] +} + +// THE PRELUDE EVERY CONSUMER LANE NEEDS: the head, then the compiler pair the `seed` job built or +// restored ONCE for the run, verified against the seed's key and promoted into this job's OWN +// checkout (target/release is private to the job, so a native probe still runs against a writable +// directory nobody else shares). The restore step keeps the id build_witness_fold because every +// downstream condition in each job reads that id: `success` there now means "the pair was verified +// and promoted", which is the precondition those conditions always stood for. +fn compiler_gate_fleet_prelude_bound_steps() -> List { + concat(compiler_gate_fleet_head_bound_steps(), [ CompilerGateBoundStep { - step: witness_floor_build_step( - step_name: build_step_name, - build_script: repo_self_build_command(bins: bins), - ), + step: ci_release_bins_download_step(), + role: capability_neutral, + step_name: "Download release-bins artifact", + }, + CompilerGateBoundStep { + step: compiler_gate_pair_restore_step(), role: consumes_only(capabilities: [CargoCapability]), - step_name: build_step_name, + step_name: compiler_gate_pair_restore_step_name, }, - ], concat(after_build, [ CompilerGateBoundStep { step: witness_toolchain_homes_isolated_guard_step(), role: capability_neutral, step_name: "Require isolated toolchain homes", }, - ])))) + ]) } // ORDER: the lint first (the cheapest verdict, over the Rust the prelude just built), then the build @@ -910,87 +909,19 @@ fn compiler_gate_floor_scripts_are_renderable() -> Bool { // ── THE MERGE QUEUE PRODUCES THE COMPILER PAIR CANDIDATE ──────────────────────────────────────── // -// The floor already builds claim_executor and gunbc from the tree that will land. On a merge group -// (the composed revision that becomes main), it also keys that build as gunbc.fleet_release_bins_key -// compiler_pair_release_product BEFORE building -- so the build is stamped with the materialized tree, -// as the preimage says -- then packs the two binaries with the build log the build step just wrote, -// wraps the pack as one object and uploads it as a short-lived workflow artifact. The floor holds no -// cache credential: it produces a CANDIDATE, and fleet-desired, after the revision lands, verifies it -// and is the only canonical publisher. A pull-request run produces nothing (nothing would consume it). -// A refusal here is the floor's: an environment the product's preimage cannot honestly describe -// stops the line rather than yielding an unprovenanced candidate. +// The `seed` job builds or restores claim_executor and gunbc for the tree that will land. On a +// merge group (the composed revision that becomes main) it also packs the two binaries with the build +// log into one object and uploads it as a short-lived workflow artifact; the key was derived by +// gunbc.fleet_release_bins_key compiler_pair_release_product BEFORE any build, so the pack is stamped +// with the materialized tree, as the preimage says. The seed holds no cache credential: it produces +// a CANDIDATE, and fleet-desired, after the revision lands, verifies it and is the only canonical +// publisher. A pull-request run produces no candidate (nothing would consume it). A refusal here is +// the seed's: an environment the product's preimage cannot honestly describe stops the line rather +// than yielding an unprovenanced candidate. fn compiler_gate_candidate_condition() -> String { "github.event_name == 'merge_group'" } -fn compiler_gate_candidate_built_condition() -> String { - "github.event_name == 'merge_group' && steps.build_witness_fold.outcome == 'success'" -} - -fn compiler_gate_candidate_run_step(name: String, script: String, condition: String) -> Step { - RunStep { - name: Present { value: name }, - id: none, - run: script, - shell: none, - env: none, - working_directory: none, - if_condition: Present { value: condition }, - continue_on_error: none, - timeout_minutes: none - } -} - -fn compiler_gate_candidate_key_bound_steps() -> List { - [ - CompilerGateBoundStep { - step: compiler_gate_candidate_run_step( - name: "Key the compiler pair candidate (merge group; stamps the build with the materialized tree)", - script: release_bins_key_script_for(product: compiler_pair_release_product()), - condition: compiler_gate_candidate_condition(), - ), - role: consumes_only(capabilities: [CargoCapability, RustcCapability]), - step_name: "Key the compiler pair candidate (merge group; stamps the build with the materialized tree)", - }, - ] -} - -fn compiler_gate_candidate_pack_bound_steps() -> List { - [ - CompilerGateBoundStep { - step: compiler_gate_candidate_run_step( - name: "Pack the compiler pair candidate with its build log", - script: join([release_bins_pack_manifest_script_for(product: compiler_pair_release_product()), release_bins_pack_object_script()], "\n"), - condition: compiler_gate_candidate_built_condition(), - ), - role: capability_neutral, - step_name: "Pack the compiler pair candidate with its build log", - }, - CompilerGateBoundStep { - step: UsesStep { - name: Present { value: "Upload the compiler pair candidate" }, - id: none, - uses: upload_artifact_action, - with: Present { - value: [ - kv(key: "name", value: yaml_string(s: compiler_pair_candidate_artifact_name)), - kv(key: "path", value: yaml_string(s: release_bins_pack_object_path_expression())), - kv(key: "if-no-files-found", value: yaml_string(s: "error")), - kv(key: "retention-days", value: yaml_int(n: compiler_pair_candidate_retention_whole_days())), - kv(key: "compression-level", value: yaml_int(n: 0)) - ] - }, - env: none, - if_condition: Present { value: compiler_gate_candidate_built_condition() }, - continue_on_error: none, - timeout_minutes: none - }, - role: capability_neutral, - step_name: "Upload the compiler pair candidate", - }, - ] -} - fn compiler_gate_floor_job() -> Job { Job { id: compiler_gate_floor_job_id, @@ -998,7 +929,7 @@ fn compiler_gate_floor_job() -> Job { runner: gunbc_ci_selected_runner_spec(), environment: none, steps: list_map(xs: compiler_gate_floor_bound_steps(), f: fn(b) { b.step }), - needs: [], + needs: [compiler_gate_seed_job_id], env: Present { value: [ kv(key: malloc_arena_max_env_key, value: yaml_string(s: malloc_arena_max_value)), @@ -1058,7 +989,7 @@ fn compiler_gate_generated_job() -> Job { runner: gunbc_ci_selected_runner_spec(), environment: none, steps: list_map(xs: compiler_gate_generated_bound_steps(), f: fn(b) { b.step }), - needs: [], + needs: [compiler_gate_seed_job_id], env: Present { value: [ kv(key: malloc_arena_max_env_key, value: yaml_string(s: malloc_arena_max_value)) @@ -1188,9 +1119,31 @@ fn compiler_gate_emit_build_subject_bound_steps() -> List // THE PRELUDE IS THE FLEET PRELUDE over `gunbc` alone, because the subject steps need exactly what it // provides on the fleet: the judged checkout, an isolated toolchain home (and the guard requiring // it), a pinned default, and the `gunbc` binary the instruments are invoked through. +// EMIT-BUILD BUILDS ITS OWN `gunbc` AND DOES NOT RESTORE THE SEED PAIR. `gunbc test` refuses a binary +// whose cargo dep-info it cannot read inside this checkout (BinaryFreshnessUndecided, #13007): a +// restored pair carries no dep-info for this worktree, so the freshness guard rightly cannot judge +// it. Until that guard can judge a restored binary, this lane keeps the one build it always paid. +fn compiler_gate_emit_build_prelude_bound_steps() -> List { + concat(compiler_gate_fleet_head_bound_steps(), [ + CompilerGateBoundStep { + step: witness_floor_build_step( + step_name: compiler_gate_build_step_name, + build_script: repo_self_build_command(bins: compiler_gate_required_bins), + ), + role: consumes_only(capabilities: [CargoCapability]), + step_name: compiler_gate_build_step_name, + }, + CompilerGateBoundStep { + step: witness_toolchain_homes_isolated_guard_step(), + role: capability_neutral, + step_name: "Require isolated toolchain homes", + }, + ]) +} + fn compiler_gate_emit_build_bound_steps() -> List { concat( - concat(compiler_gate_fleet_prelude_bound_steps_around_build(bins: compiler_gate_required_bins, build_step_name: compiler_gate_build_step_name, before_build: [], after_build: []), compiler_gate_emit_build_subject_bound_steps()), + concat(compiler_gate_emit_build_prelude_bound_steps(), compiler_gate_emit_build_subject_bound_steps()), [CompilerGateBoundStep { step: compiler_gate_emit_build_notice_step(), role: capability_neutral, @@ -1232,6 +1185,219 @@ fn compiler_gate_emit_build_job() -> Job { } } +// ═══════════════════════════════════════════════════════════════════════════ +// THE SEED LANE: THE COMPILER PAIR IS BUILT OR RESTORED ONCE PER RUN. +// +// OPERATOR DIRECTION 2026-10-06 (CI programme item 1, rows R1/R2; operator authorized changing the required job roster for this programme): +// the three fleet lanes each built the same two binaries from the same tree, so a run paid the +// release build three times on three runners. The pair is one product keyed by +// gunbc.fleet_release_bins_key compiler_pair_release_product -- the same keyed materialization +// fleet-converge's build job uses, so the cache lookup, the restore into staging, the complete +// verification before any restored binary runs and the build-log member are that module's, not +// re-derived here. This job derives the key, looks it up (admitted refs only), builds ONLY on a miss +// (release_bins_build_condition), packs on a build and hands the pack to the three consumers as a +// workflow artifact. A consumer verifies the pack against this job's key and promotes it into its +// OWN checkout's target/release, so every consumer still runs the binaries from a private writable +// directory; nothing is shared by path. +// +// A MISS IS THE FULL BUILD, NOT A LOSS OF COVERAGE: the build line is the product's own build_line, +// the very text the key names. A lookup that cannot answer is not absence (the lookup step says so), +// and a pack that does not verify refuses. The job is a roster growth the operator authorized for +// this programme; it replaces two of three builds per run. +data compiler_gate_seed_job_id: String = "seed" + +data compiler_gate_seed_build_step_name: String = "Build the compiler pair (keyed miss only)" +data compiler_gate_pair_restore_step_name: String = "Verify the seed's compiler pair against its key and promote it into this checkout" + +fn compiler_gate_seed_run_step(name: String, id: String?, script: String, env: List, condition: String?, timeout: Int?) -> Step { + RunStep { + name: Present { value: name }, + id: id, + run: script, + shell: none, + env: if length(xs: env) == 0 { none } else { Present { value: env } }, + working_directory: none, + if_condition: condition, + continue_on_error: none, + timeout_minutes: timeout + } +} + +fn compiler_gate_seed_key_expression() -> String { + join(["$", "{{ needs.", compiler_gate_seed_job_id, ".outputs.", release_bins_key_job_output, " }}"], "") +} + +fn compiler_gate_pair_restore_step() -> Step { + compiler_gate_seed_run_step( + name: compiler_gate_pair_restore_step_name, + id: Present { value: "build_witness_fold" }, + script: release_bins_consumer_key_check_script_for(product: compiler_pair_release_product()), + env: [kv(key: "RELEASE_BINS_KEY", value: yaml_string(s: compiler_gate_seed_key_expression()))], + condition: none, + timeout: Present { value: gunbc_ci_aux_step_timeout_minutes }, + ) +} + +fn compiler_gate_seed_bound_steps() -> List { + concat(compiler_gate_fleet_head_bound_steps(), [ + CompilerGateBoundStep { + step: compiler_gate_seed_run_step( + name: "Key the compiler pair (stamps the build with the materialized tree)", + id: Present { value: release_bins_key_step_id }, + script: release_bins_key_script_for(product: compiler_pair_release_product()), + env: [], + condition: none, + timeout: Present { value: gunbc_ci_aux_step_timeout_minutes }, + ), + role: consumes_only(capabilities: [CargoCapability, RustcCapability]), + step_name: "Key the compiler pair (stamps the build with the materialized tree)", + }, + CompilerGateBoundStep { + step: ci_release_bins_lookup_step(), + role: capability_neutral, + step_name: "Look up the compiler pair by exact key", + }, + CompilerGateBoundStep { + step: ci_release_bins_cache_step(), + role: capability_neutral, + step_name: "Restore the compiler pair into isolated staging", + }, + CompilerGateBoundStep { + step: compiler_gate_seed_run_step( + name: "Decide the compiler pair outcome; verify a stored pack completely before any restored binary runs", + id: Present { value: release_bins_outcome_step_id }, + script: release_bins_outcome_script_for(product: compiler_pair_release_product()), + env: [ + kv(key: "RELEASE_BINS_KEY", value: yaml_string(s: release_bins_key_expression())), + kv(key: "LOOKUP_STANDING", value: yaml_string(s: release_bins_lookup_standing_expression())), + kv(key: "LOOKUP_ENTRY", value: yaml_string(s: release_bins_lookup_entry_expression())), + kv(key: "CACHE_HIT", value: yaml_string(s: release_bins_cache_hit_expression())), + ], + condition: none, + timeout: Present { value: gunbc_ci_aux_step_timeout_minutes }, + ), + role: consumes_only(capabilities: [CargoCapability]), + step_name: "Decide the compiler pair outcome", + }, + CompilerGateBoundStep { + step: ci_cache_cargo_step(), + role: capability_neutral, + step_name: "Cache Cargo", + }, + CompilerGateBoundStep { + step: compiler_gate_seed_run_step( + name: compiler_gate_seed_build_step_name, + id: Present { value: "build_witness_fold" }, + script: concat(wrap_command_with_floor_attempt_receipt(command: compiler_pair_release_product().build_line), "\n"), + env: [], + condition: Present { value: release_bins_build_condition() }, + timeout: none, + ), + role: consumes_only(capabilities: [CargoCapability]), + step_name: compiler_gate_seed_build_step_name, + }, + CompilerGateBoundStep { + step: compiler_gate_seed_run_step( + name: "Pack the compiler pair with its build log (build outcomes only)", + id: none, + script: release_bins_pack_manifest_script_for(product: compiler_pair_release_product()), + env: [kv(key: "RELEASE_BINS_KEY", value: yaml_string(s: release_bins_key_expression()))], + condition: Present { value: release_bins_build_condition() }, + timeout: Present { value: gunbc_ci_aux_step_timeout_minutes }, + ), + role: capability_neutral, + step_name: "Pack the compiler pair with its build log (build outcomes only)", + }, + CompilerGateBoundStep { + step: ci_release_bins_upload_step(), + role: capability_neutral, + step_name: "Upload release-bins artifact", + }, + CompilerGateBoundStep { + step: compiler_gate_seed_run_step( + name: "Wrap the compiler pair as the merge-group candidate object", + id: none, + script: release_bins_pack_object_script(), + env: [], + condition: Present { value: compiler_gate_candidate_condition() }, + timeout: Present { value: gunbc_ci_aux_step_timeout_minutes }, + ), + role: capability_neutral, + step_name: "Wrap the compiler pair as the merge-group candidate object", + }, + CompilerGateBoundStep { + step: UsesStep { + name: Present { value: "Upload the compiler pair candidate" }, + id: none, + uses: upload_artifact_action, + with: Present { + value: [ + kv(key: "name", value: yaml_string(s: compiler_pair_candidate_artifact_name)), + kv(key: "path", value: yaml_string(s: release_bins_pack_object_path_expression())), + kv(key: "if-no-files-found", value: yaml_string(s: "error")), + kv(key: "retention-days", value: yaml_int(n: compiler_pair_candidate_retention_whole_days())), + kv(key: "compression-level", value: yaml_int(n: 0)) + ] + }, + env: none, + if_condition: Present { value: compiler_gate_candidate_condition() }, + continue_on_error: none, + timeout_minutes: none + }, + role: capability_neutral, + step_name: "Upload the compiler pair candidate", + }, + CompilerGateBoundStep { + step: witness_toolchain_homes_isolated_guard_step(), + role: capability_neutral, + step_name: "Require isolated toolchain homes", + }, + ]) +} + +fn compiler_gate_seed_capability_closure_holds() -> Bool { + capability_closure_is_closed( + v: workflow_job_capability_closure( + steps: list_map( + xs: compiler_gate_seed_bound_steps(), + f: fn(b) { CapabilityAnnotatedStep { step_name: b.step_name, role: b.role } }, + ) + ), + ) +} + +fn compiler_gate_seed_job() -> Job { + Job { + id: compiler_gate_seed_job_id, + name: none, + runner: gunbc_ci_selected_runner_spec(), + environment: none, + steps: list_map(xs: compiler_gate_seed_bound_steps(), f: fn(b) { b.step }), + needs: [], + env: Present { + value: [ + kv(key: malloc_arena_max_env_key, value: yaml_string(s: malloc_arena_max_value)) + ] + }, + outputs: Present { + value: [kv(key: release_bins_key_job_output, value: yaml_string(s: release_bins_key_expression()))] + }, + if_condition: Present { value: compiler_gate_floor_job_condition() }, + timeout_minutes: Present { value: compiler_gate_floor_timeout_minutes }, + continue_on_error: none, + concurrency: none, + permissions: Present { + value: WorkflowPermissions { + contents: Present { value: PermRead }, + pull_requests: none, + issues: none, + actions: Present { value: PermRead }, + id_token: none, + } + } + } +} + // A LANE RESULT IS AN EXPRESSION, NOT SHELL TEXT, AND THE JOB ID IS NOT ALWAYS DOT-SAFE. // // `-` is the SUBTRACTION OPERATOR in the Actions expression language, so `needs.emit-build.result` @@ -1561,6 +1727,15 @@ fn compiler_gate_lane_rows() -> List { }, standing: LaneBlocks, }, + CompilerGateLaneRow { + job_id: compiler_gate_seed_job_id, + var_name: "SEED", + standing: LaneBlocks, + arm: GateArmSkippableOnFork { + notice: "the seed lane is skipped for a fork pull request: no compiler pair was built or restored for this head", + unobserved_error: "the seed lane produced no success" + } + }, CompilerGateLaneRow { job_id: compiler_gate_emit_build_job_id, var_name: "EMIT_BUILD", @@ -1720,7 +1895,7 @@ data compiler_gate_workflow: Workflow = Workflow { kv(key: (required_ci_contract_epoch_env_key as String), value: yaml_string(s: (required_ci_contract_epoch as String))) ] }, permissions: Present { value: compiler_gate_permissions }, - jobs: [compiler_gate_unit_test_job(), compiler_gate_emit_build_job(), compiler_gate_floor_job(), compiler_gate_generated_job(), compiler_gate_aggregate_job()] + jobs: [compiler_gate_unit_test_job(), compiler_gate_seed_job(), compiler_gate_emit_build_job(), compiler_gate_floor_job(), compiler_gate_generated_job(), compiler_gate_aggregate_job()] } data compiler_gate_capability_refusal_reason: String = "REFUSED gunbc.compiler_gate_workflow: a job's capability closure does not hold, a floor script is not renderable, the workflow runs a job that compiler_gate_lane_rows does not roster, or a blocking lane or the aggregate runs on a hosted runner (compiler_gate_merge_path_runs_on_the_fleet, operator ruling 2026-09-28). No workflow yaml was emitted. Fix the step roles in the lane's bound steps, add the job's row, or put the merge-path job on gunbc_ci_selected_runner_spec. The gate's needs, env bindings and every conditional are FOLDED from that roster, so they cannot disagree with it and this refusal has nothing to say about them." @@ -1738,7 +1913,7 @@ fn expected_compiler_gate_yml() -> WitnessFloorGenerationOutcome { WorkflowToolchainHomeRefused { job_id: j, refusal: r } => WitnessFloorGenerationRefused { reason: toolchain_home_refusal_reason(job_id: j, refusal: r) } WorkflowToolchainHomesAdmitted => - if compiler_gate_unit_test_capability_closure_holds() && compiler_gate_emit_build_capability_closure_holds() && compiler_gate_floor_capability_closure_holds() && compiler_gate_generated_capability_closure_holds() && compiler_gate_floor_scripts_are_renderable() && compiler_gate_lane_standings_hold() && compiler_gate_merge_path_runs_on_the_fleet() { + if compiler_gate_unit_test_capability_closure_holds() && compiler_gate_emit_build_capability_closure_holds() && compiler_gate_seed_capability_closure_holds() && compiler_gate_floor_capability_closure_holds() && compiler_gate_generated_capability_closure_holds() && compiler_gate_floor_scripts_are_renderable() && compiler_gate_lane_standings_hold() && compiler_gate_merge_path_runs_on_the_fleet() { match emit_yaml(v: project_workflow_to_yaml(workflow: compiler_gate_workflow)) { EmittedYaml { text: t } => WitnessFloorGenerated { content: t } YamlEmitRefused { path: p, reason: r } => WitnessFloorGenerationRefused { reason: yaml_emit_refusal_text(module_path: "gunbc.compiler_gate_workflow", path: p, reason: r) } diff --git a/src/v1/stage0/src/bin/claim_executor.rs b/src/v1/stage0/src/bin/claim_executor.rs index 4a032c679c7..d335199830e 100644 --- a/src/v1/stage0/src/bin/claim_executor.rs +++ b/src/v1/stage0/src/bin/claim_executor.rs @@ -198,6 +198,7 @@ fn run() -> Result { let mut source_roots: Vec = Vec::new(); let mut verify_artifacts: Vec = Vec::new(); let mut verify_artifacts_mode = false; + let mut write_checker_inputs: Option = None; let mut required_floor_mode = false; let mut required_ci_mode = false; let mut required_ci_measurement_receipt: Option = None; @@ -232,6 +233,13 @@ fn run() -> Result { } break; } + // THE PRODUCER OF A RESTORED PAIR'S CHECKER-INPUT RECORD, run by the job that built the + // pair (see `write_checker_inputs_record`). Takes no roots. + "--write-checker-inputs" => { + i += 1; + // An OUTPUT path: it does not exist yet, so it is not resolved-and-required. + write_checker_inputs = Some(require_value(&args, i, "--write-checker-inputs")?); + } "--source-root" => { i += 1; source_roots.push(require_path_value(&args, i, "--source-root")?); @@ -375,6 +383,20 @@ fn run() -> Result { return verify_build_artifacts(&verify_artifacts); } + if let Some(path) = write_checker_inputs { + return match v1_compiler::cli_run::write_checker_inputs_record(std::path::Path::new(&path)) + { + Ok(n) => { + eprintln!("claim_executor: wrote {n} checker inputs to {path}"); + Ok(ExitCode::SUCCESS) + } + Err(e) => { + eprintln!("::error::write-checker-inputs refused: {e}"); + Err(ExitCode::from(1)) + } + }; + } + if let Some(path) = reach_base_standings_file { let Some(wall_ms) = claim_wall_limit_ms else { eprintln!("--reach-base-standings requires --claim-wall-limit-ms"); diff --git a/src/v1/stage0/src/cli_run.rs b/src/v1/stage0/src/cli_run.rs index 0bf1b1909c6..ed03a2fed7c 100644 --- a/src/v1/stage0/src/cli_run.rs +++ b/src/v1/stage0/src/cli_run.rs @@ -86,6 +86,7 @@ use serde::Serialize; mod active_workset; mod census_heads; mod checker_dependency; +pub use checker_dependency::write_checker_inputs_record; #[path = "declaration_index.rs"] pub mod declaration_index; pub mod derived_row_roster; diff --git a/src/v1/stage0/src/cli_run/checker_dependency.rs b/src/v1/stage0/src/cli_run/checker_dependency.rs index 0bb42017a9b..d51f1ce2b0e 100644 --- a/src/v1/stage0/src/cli_run/checker_dependency.rs +++ b/src/v1/stage0/src/cli_run/checker_dependency.rs @@ -121,25 +121,98 @@ pub(crate) fn checker_input_paths(prerequisites: &[String], workspace_root: &Pat inputs.into_iter().collect() } -/// Observe the checker's inputs from the dep-info beside the running executable. A missing, -/// unreadable or foreign record is a refusal the `.dag` rule consumes, never an empty set. +/// THE SIDECAR OF A RESTORED PAIR: `/_checker_inputs`, one workspace-relative +/// path per line. It is written by `write_checker_inputs_record` in the job that BUILT the pair, from +/// that build's own cargo dep-info, and travels as a verified member of the release-bins pack +/// (`gunbc.fleet_release_bins_key` `compiler_pair_release_product` `sidecars`), so a consumer that +/// restores the pair has it only after the pack's source closure was verified equal to its own tree. +/// It is a second FORM of the same fact the dep-info records, never a rebased copy of it. +pub(crate) fn restored_checker_inputs_path(exe: &Path) -> PathBuf { + let stem = exe + .file_stem() + .map(|s| s.to_string_lossy().into_owned()) + .unwrap_or_default(); + exe.with_file_name(format!("{stem}_checker_inputs")) +} + +/// Observe the checker's inputs: cargo's dep-info beside the running executable when this workspace +/// built it, else the verified sidecar of a restored pair. A missing, unreadable or foreign record +/// is a refusal the `.dag` rule consumes, never an empty set; the dep-info form wins when both +/// exist, because it is the build tool's own record rather than a carried one. pub(crate) fn observe_checker_input_paths(workspace_root: &Path) -> Result, String> { let exe = std::env::current_exe() .map_err(|e| format!("the running checker's own path is unreadable: {e}"))?; let mut dep_info = exe.clone().into_os_string(); dep_info.push(".d"); let dep_info = PathBuf::from(dep_info); + let text = match std::fs::read_to_string(&dep_info) { + Ok(text) => text, + Err(e) => { + let sidecar = restored_checker_inputs_path(&exe); + return match std::fs::read_to_string(&sidecar) { + Ok(carried) => checker_inputs_from_sidecar(&carried, &sidecar), + Err(_) => Err(format!( + "the checker's dependency record {} is unreadable ({e}) and no restored-pair \ + record {} exists; the floor must run a binary cargo built in this workspace \ + or restored from a verified pack", + dep_info.display(), + sidecar.display() + )), + }; + } + }; + let canonical_root = workspace_root + .canonicalize() + .unwrap_or_else(|_| workspace_root.to_path_buf()); + checker_inputs_from_record(&text, &dep_info, &canonical_root) +} + +/// The checker's inputs from a restored pair's sidecar text. The same anchor as the dep-info form +/// applies: a record that omits the file compiled into this binary describes another build. +pub(crate) fn checker_inputs_from_sidecar( + text: &str, + record: &Path, +) -> Result, String> { + let inputs: Vec = text + .lines() + .map(str::trim) + .filter(|l| !l.is_empty()) + .map(normalize_repo_path) + .collect(); + let anchor = normalize_repo_path(file!()); + if !inputs.contains(&anchor) { + return Err(format!( + "the restored-pair record {} does not list {anchor}, which this binary was compiled \ + from; it describes another build ({} inputs)", + record.display(), + inputs.len() + )); + } + Ok(inputs) +} + +/// THE PRODUCER OF THE SIDECAR: the checker's inputs from THIS build's own dep-info (the `.d` form +/// only, so a sidecar is never re-derived from a sidecar), written one path per line. Refuses when +/// the dep-info is not the cargo form. +pub fn write_checker_inputs_record(out: &Path) -> Result { + let exe = std::env::current_exe() + .map_err(|e| format!("the running checker's own path is unreadable: {e}"))?; + let mut dep_info = exe.into_os_string(); + dep_info.push(".d"); + let dep_info = PathBuf::from(dep_info); let text = std::fs::read_to_string(&dep_info).map_err(|e| { format!( - "the checker's dependency record {} is unreadable ({e}); the floor must run a binary \ - cargo built in this workspace", + "the checker's dependency record {} is unreadable ({e}); only a binary cargo built in \ + this workspace can produce the restored-pair record", dep_info.display() ) })?; - let canonical_root = workspace_root - .canonicalize() - .unwrap_or_else(|_| workspace_root.to_path_buf()); - checker_inputs_from_record(&text, &dep_info, &canonical_root) + let root = process_workspace_root(); + let root = root.canonicalize().unwrap_or(root); + let inputs = checker_inputs_from_record(&text, &dep_info, &root)?; + std::fs::write(out, inputs.join("\n") + "\n") + .map_err(|e| format!("cannot write {}: {e}", out.display()))?; + Ok(inputs.len()) } /// The checker's inputs from one record's text, or the reason the record cannot stand for them. @@ -446,6 +519,34 @@ mod tests { assert!(refusal.contains("does not list"), "{refusal}"); } + // A RESTORED PAIR'S SIDECAR is admitted only with the anchor, exactly as the dep-info form is: + // a record from another build is refused instead of read as "no checker input changed". + #[test] + fn a_restored_pair_record_is_admitted_only_with_its_anchor() { + let with_anchor = format!("src/v1/stage0/Cargo.toml\n{}\n", file!()); + let inputs = + checker_inputs_from_sidecar(&with_anchor, Path::new("claim_executor_checker_inputs")) + .expect("a record listing the anchor is the checker's inputs"); + assert!(inputs.contains(&normalize_repo_path(file!()))); + let refusal = checker_inputs_from_sidecar( + "src/v1/stage0/Cargo.toml\n", + Path::new("claim_executor_checker_inputs"), + ) + .expect_err("a record omitting the running checker's own file is refused"); + assert!(refusal.contains("does not list"), "{refusal}"); + let empty = checker_inputs_from_sidecar("", Path::new("claim_executor_checker_inputs")) + .expect_err("an empty record is refused, never read as no checker input changed"); + assert!(empty.contains("does not list"), "{empty}"); + } + + #[test] + fn the_restored_pair_record_sits_beside_the_binary_under_its_stem() { + assert_eq!( + restored_checker_inputs_path(Path::new("/w/target/release/claim_executor")), + PathBuf::from("/w/target/release/claim_executor_checker_inputs") + ); + } + fn tempdir_with_manifest() -> PathBuf { let root = std::env::temp_dir().join(format!( "checker_dependency_test_{}_{}",