@@ -48,6 +48,8 @@ CI_CHECK="$ROOT/scripts/ci/ci-check.sh"
4848RUN_RUST_JOB=" $ROOT /scripts/ci/run-rust-job.sh"
4949RELEASE_PREFLIGHT=" $ROOT /scripts/ci/release-preflight.sh"
5050RELEASE_EVIDENCE=" $ROOT /scripts/ci/release-evidence-check.sh"
51+ RELEASE_CT_RECOVERY=" $ROOT /scripts/ci/release-ct-recovery-check.sh"
52+ RELEASE_CT_RECOVERY_TEST=" $ROOT /scripts/ci/release-ct-recovery-check-test.sh"
5153RELEASE_SOURCE=" $ROOT /scripts/ci/package-release-source.sh"
5254RELEASE_MANIFEST=" $ROOT /scripts/ci/write-release-manifest.sh"
5355RELEASE_IDENTITY_TEST=" $ROOT /scripts/ci/release-identity-test.sh"
@@ -146,6 +148,8 @@ require_file "$CI_CHECK"
146148require_file " $RUN_RUST_JOB "
147149require_file " $RELEASE_PREFLIGHT "
148150require_file " $RELEASE_EVIDENCE "
151+ require_file " $RELEASE_CT_RECOVERY "
152+ require_file " $RELEASE_CT_RECOVERY_TEST "
149153require_file " $RELEASE_SOURCE "
150154require_file " $RELEASE_MANIFEST "
151155require_file " $RELEASE_IDENTITY_TEST "
@@ -234,6 +238,16 @@ done < <(
234238 || fail " the reusable Rust job operation must be required"
235239[[ $(yq eval '.on.workflow_call.inputs.operation.type' "$RUST_JOB") == "string" ]] \
236240 || fail " the reusable Rust job operation must be typed as a string"
241+ [[ $(yq eval '.on.workflow_call.inputs.checkout_ref.type' "$RUST_JOB") == "string" ]] \
242+ || fail " the reusable Rust job checkout ref must be typed as a string"
243+ # shellcheck disable=SC2016 # GitHub expression is an intentional literal workflow contract.
244+ [[ $(yq eval '.jobs.run.steps[] | select(.name == " Checkout" ) | .with.ref' "$RUST_JOB") \
245+ == ' ${{ inputs.checkout_ref || github.sha } } ' ]] \
246+ || fail " the reusable Rust job must bind an explicit source ref before execution"
247+ # shellcheck disable=SC2016 # GitHub expression is an intentional literal workflow contract.
248+ [[ $( yq eval ' .jobs.run.steps[] | select(.name == "Run") | .env.CARGO_TARGET_S390X_UNKNOWN_LINUX_GNU_RUSTFLAGS' " $RUST_JOB " ) \
249+ == '${{ inputs.target == ' \' ' s390x-unknown-linux-gnu' \' ' && ' \' ' -C target-feature=+vector' \' ' || ' \' ' ' \' ' }}' ]] \
250+ || fail " s390x CT jobs must share one explicit vector target environment"
237251[[ $(yq eval '[.jobs.run.steps[] | select(has(" run" ))] | length' "$RUST_JOB") -eq 1 ]] \
238252 || fail "the reusable Rust job must expose one fixed command step"
239253[[ $(yq eval ' .jobs.run.steps[] | select(has(" run" )) | .run' "$RUST_JOB") == "scripts/ci/run-rust-job.sh" ]] \
@@ -413,6 +427,8 @@ grep -Eq 'HOST_ARGS\+=\(--feature-matrix\)' "$CHECK_ALL" \
413427 || fail "release recovery must require an explicit existing tag"
414428[[ $(yq eval ' .on.workflow_dispatch.inputs.tag.type' "$RELEASE") == "string" ]] \
415429 || fail "release recovery tag input must be a string"
430+ [[ $(yq eval ' .on.workflow_dispatch.inputs.s390x_ct_run.type' "$RELEASE") == "string" ]] \
431+ || fail "release recovery s390x CT run input must be a string"
416432# shellcheck disable=SC2016 # GitHub expressions are intentional literal workflow contracts.
417433[[ $(yq eval ' .jobs.preflight.steps[] | select(.name == " Checkout" ) | .with.ref' "$RELEASE") \
418434 == ' ${{ github.event_name == ' \' ' workflow_dispatch' \' ' && inputs.tag || github.ref }}' ]] \
@@ -427,6 +443,11 @@ grep -Fq 'refs/heads/main' <<<"$identity_step" \
427443recovery_tool_step=$(yq eval ' .jobs.preflight.steps[] | select(.name == " Install recovery SemVer checker" ) | .run' "$RELEASE")
428444grep -Fq ' install-tools.sh" semver' <<<" $recovery_tool_step " \
429445 || fail " release recovery must use the authenticated SemVer tool installer"
446+ ct_recovery_step=$( yq eval ' .jobs.preflight.steps[] | select(.name == "Verify s390x CT recovery evidence") | .run' " $RELEASE " )
447+ grep -Fq 'release-ct-recovery-check.sh' <<<" $ct_recovery_step " \
448+ || fail " release recovery must validate replacement s390x CT evidence"
449+ grep -Fq -- '--workflow-commit " $WORKFLOW_COMMIT " ' <<<" $ct_recovery_step " \
450+ || fail " replacement s390x CT evidence must come from the reviewed workflow commit"
430451# shellcheck disable=SC2016 # ` $crate ` is an intentional literal in the release-preflight contract regex.
431452[[ $( count_matches ' cargo semver-checks --package "\$crate" --all-features' " $RELEASE_PREFLIGHT " ) -eq 1 ]] \
432453 || fail " tag preflight must have exactly one final-version SemVer owner"
@@ -626,6 +647,28 @@ grep -Fq 'run-id: ${{ needs.preflight.outputs.weekly_run_id }}' "$RELEASE" \
626647 || fail " release must consume non-RISC-V CT artifacts from the validated Weekly run"
627648grep -Fq 'run-id: ${{ needs.preflight.outputs.riscv_run_id } }' " $RELEASE " \
628649 || fail " release must consume RISC-V CT artifacts from the validated RISC-V run"
650+ grep -Fq 'run-id: ${{ needs.preflight.outputs.s390x_ct_run_id } }' " $RELEASE " \
651+ || fail " release must consume recovered s390x CT artifacts from the validated recovery run"
652+ [[ $( yq eval ' .on.workflow_dispatch.inputs.release_tag.type' " $CT " ) == " string" ]] \
653+ || fail " CT recovery release tag input must be a string"
654+ ct_source_step=$( yq eval ' .jobs.plan.steps[] | select(.name == "Resolve CT source") | .run' " $CT " )
655+ grep -Fq 'refs/heads/main' <<<" $ct_source_step " \
656+ || fail " release CT recovery must reject workflow code outside protected main"
657+ grep -Fq 'checkout_ref=$RELEASE_TAG ' <<<" $ct_source_step " \
658+ || fail " release CT recovery must bind execution to the immutable tag"
659+ grep -Fq 'PLATFORMS" ! = " ibm-s390x' <<<" $ct_source_step " \
660+ || fail " release CT recovery must be limited to the s390x lane"
661+ grep -Fq 'DUDECT_TIMEOUT" ! = " 1800" ' <<<"$ct_source_step" \
662+ || fail "release CT recovery must preserve the release DudeCT timeout"
663+ grep -Fq ' BINSEC_TIMEOUT" != " 900" ' <<<" $ct_source_step " \
664+ || fail " release CT recovery must preserve the release BINSEC timeout"
665+ grep -Fq 'UPLOAD_RAW_ARTIFACTS" ! = " true" ' <<<"$ct_source_step" \
666+ || fail "release CT recovery must retain raw evidence"
667+ grep -Fq ' ARTIFACT_RETENTION_DAYS" != " 90" ' <<<" $ct_source_step " \
668+ || fail " release CT recovery must retain evidence for the release lifetime"
669+ # shellcheck disable=SC2016 # GitHub expression is an intentional literal workflow contract.
670+ [[ $( yq eval ' .jobs.ct.with.checkout_ref' " $CT " ) == '${{ needs.plan.outputs.checkout_ref } }' ]] \
671+ || fail " release CT recovery must execute the resolved immutable tag source"
629672if grep -Eq 'uses: ./\.github/workflows/(ct|rsa)\.yaml' " $RELEASE " ; then
630673 fail " tag workflow must promote exact-commit evidence instead of rerunning CT or RSA"
631674fi
0 commit comments