name: Build Release Candidate on: workflow_call: inputs: version: description: 'Version number without the leading v' required: true type: string require_macos_signing: description: 'Require Developer ID signed and notarized macOS agent binaries' required: false default: false type: boolean require_windows_signing: description: 'Require Authenticode-signed Windows agent binaries' required: false default: false type: boolean windows_signing_backend: description: 'Windows signing backend: signpath (canonical) or legacy-pfx (break-glass fallback)' required: false default: signpath type: string outputs: artifact_name: description: 'Immutable release candidate artifact name' value: ${{ jobs.build.outputs.artifact_name }} manifest_artifact_name: description: 'Release candidate manifest artifact name' value: ${{ jobs.build.outputs.manifest_artifact_name }} windows_signing_backend: description: 'Windows signing backend used for the candidate' value: ${{ jobs.sign-windows-agent.outputs.signing_backend }} permissions: contents: read jobs: signing-configuration: name: Verify Native Signing Configuration if: ${{ inputs.require_macos_signing || inputs.require_windows_signing }} runs-on: ubuntu-24.04 timeout-minutes: 2 steps: - name: Report missing signing secrets env: APPLE_DEVELOPER_ID_CERTIFICATE_P12_BASE64: ${{ secrets.APPLE_DEVELOPER_ID_CERTIFICATE_P12_BASE64 }} APPLE_DEVELOPER_ID_CERTIFICATE_PASSWORD: ${{ secrets.APPLE_DEVELOPER_ID_CERTIFICATE_PASSWORD }} APPLE_DEVELOPER_ID_APPLICATION_IDENTITY: ${{ secrets.APPLE_DEVELOPER_ID_APPLICATION_IDENTITY }} APPLE_NOTARY_KEY_P8_BASE64: ${{ secrets.APPLE_NOTARY_KEY_P8_BASE64 }} APPLE_NOTARY_KEY_ID: ${{ secrets.APPLE_NOTARY_KEY_ID }} APPLE_NOTARY_ISSUER_ID: ${{ secrets.APPLE_NOTARY_ISSUER_ID }} WINDOWS_CODE_SIGNING_CERTIFICATE_PFX_BASE64: ${{ secrets.WINDOWS_CODE_SIGNING_CERTIFICATE_PFX_BASE64 }} WINDOWS_CODE_SIGNING_CERTIFICATE_PASSWORD: ${{ secrets.WINDOWS_CODE_SIGNING_CERTIFICATE_PASSWORD }} SIGNPATH_API_TOKEN: ${{ secrets.SIGNPATH_API_TOKEN }} SIGNPATH_ORGANIZATION_ID: ${{ vars.SIGNPATH_ORGANIZATION_ID }} SIGNPATH_PROJECT_SLUG: ${{ vars.SIGNPATH_PROJECT_SLUG }} SIGNPATH_SIGNING_POLICY_SLUG: ${{ vars.SIGNPATH_SIGNING_POLICY_SLUG }} SIGNPATH_ARTIFACT_CONFIGURATION_SLUG: ${{ vars.SIGNPATH_ARTIFACT_CONFIGURATION_SLUG }} SIGNPATH_EXPECTED_CERTIFICATE_SUBJECT: ${{ vars.SIGNPATH_EXPECTED_CERTIFICATE_SUBJECT }} REQUIRE_MACOS_SIGNING: ${{ inputs.require_macos_signing }} REQUIRE_WINDOWS_SIGNING: ${{ inputs.require_windows_signing }} WINDOWS_SIGNING_BACKEND: ${{ inputs.windows_signing_backend }} run: | set -euo pipefail missing=0 required=() if [[ "$REQUIRE_MACOS_SIGNING" == "true" ]]; then required+=( APPLE_DEVELOPER_ID_CERTIFICATE_P12_BASE64 APPLE_DEVELOPER_ID_CERTIFICATE_PASSWORD APPLE_DEVELOPER_ID_APPLICATION_IDENTITY APPLE_NOTARY_KEY_P8_BASE64 APPLE_NOTARY_KEY_ID APPLE_NOTARY_ISSUER_ID ) fi if [[ "$REQUIRE_WINDOWS_SIGNING" == "true" ]]; then case "$WINDOWS_SIGNING_BACKEND" in signpath) required+=( SIGNPATH_API_TOKEN SIGNPATH_ORGANIZATION_ID SIGNPATH_PROJECT_SLUG SIGNPATH_SIGNING_POLICY_SLUG SIGNPATH_ARTIFACT_CONFIGURATION_SLUG SIGNPATH_EXPECTED_CERTIFICATE_SUBJECT ) ;; legacy-pfx) required+=( WINDOWS_CODE_SIGNING_CERTIFICATE_PFX_BASE64 WINDOWS_CODE_SIGNING_CERTIFICATE_PASSWORD ) echo "::warning::Using the break-glass legacy PFX Windows signing backend." ;; *) echo "::error::Unsupported Windows signing backend '${WINDOWS_SIGNING_BACKEND}'. Expected signpath or legacy-pfx." missing=1 ;; esac else echo "::notice::Windows Authenticode is not required for this candidate." fi for name in "${required[@]}"; do if [ -z "${!name:-}" ]; then echo "::error::Missing required Actions secret ${name}." missing=1 fi done exit "$missing" sign-macos-agent: name: Sign and Notarize macOS Agent needs: signing-configuration if: ${{ inputs.require_macos_signing && needs.signing-configuration.result == 'success' }} runs-on: macos-15 timeout-minutes: 30 steps: - name: Checkout repository uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3 - name: Set up Go uses: actions/setup-go@4a3601121dd01d1626a1e23e37211e3254c1c06c # v6.4.0 with: go-version-file: go.mod cache: true - name: Build, sign, and notarize agent binaries shell: bash env: APPLE_CERTIFICATE_P12_BASE64: ${{ secrets.APPLE_DEVELOPER_ID_CERTIFICATE_P12_BASE64 }} APPLE_CERTIFICATE_PASSWORD: ${{ secrets.APPLE_DEVELOPER_ID_CERTIFICATE_PASSWORD }} APPLE_SIGNING_IDENTITY: ${{ secrets.APPLE_DEVELOPER_ID_APPLICATION_IDENTITY }} APPLE_NOTARY_KEY_P8_BASE64: ${{ secrets.APPLE_NOTARY_KEY_P8_BASE64 }} APPLE_NOTARY_KEY_ID: ${{ secrets.APPLE_NOTARY_KEY_ID }} APPLE_NOTARY_ISSUER_ID: ${{ secrets.APPLE_NOTARY_ISSUER_ID }} PULSE_UPDATE_SIGNING_PUBLIC_KEY: ${{ vars.PULSE_UPDATE_SIGNING_PUBLIC_KEY }} run: | set -euo pipefail for name in \ APPLE_CERTIFICATE_P12_BASE64 \ APPLE_CERTIFICATE_PASSWORD \ APPLE_SIGNING_IDENTITY \ APPLE_NOTARY_KEY_P8_BASE64 \ APPLE_NOTARY_KEY_ID \ APPLE_NOTARY_ISSUER_ID \ PULSE_UPDATE_SIGNING_PUBLIC_KEY; do test -n "${!name:-}" || { echo "::error::Missing required ${name}."; exit 1; } done mkdir -p native-agent-binaries python3 - <<'PY' import base64, os from pathlib import Path Path('developer-id.p12').write_bytes(base64.b64decode(os.environ['APPLE_CERTIFICATE_P12_BASE64'])) Path('AuthKey.p8').write_bytes(base64.b64decode(os.environ['APPLE_NOTARY_KEY_P8_BASE64'])) PY keychain="$RUNNER_TEMP/pulse-signing.keychain-db" keychain_password="$(openssl rand -hex 24)" security create-keychain -p "$keychain_password" "$keychain" security set-keychain-settings -lut 21600 "$keychain" security unlock-keychain -p "$keychain_password" "$keychain" security import developer-id.p12 -k "$keychain" -P "$APPLE_CERTIFICATE_PASSWORD" -T /usr/bin/codesign security set-key-partition-list -S apple-tool:,apple: -s -k "$keychain_password" "$keychain" security list-keychains -d user -s "$keychain" ldflags="$(./scripts/release_ldflags.sh agent \ --version "v${{ inputs.version }}" \ --update-public-keys "$PULSE_UPDATE_SIGNING_PUBLIC_KEY")" for arch in amd64 arm64; do output="native-agent-binaries/pulse-agent-darwin-${arch}" GOOS=darwin GOARCH="$arch" CGO_ENABLED=0 \ go build -buildvcs=false -trimpath -ldflags="$ldflags" -o "$output" ./cmd/pulse-agent codesign --force --timestamp --options runtime --sign "$APPLE_SIGNING_IDENTITY" "$output" codesign --verify --deep --strict --verbose=2 "$output" done ditto -c -k --keepParent native-agent-binaries pulse-agent-macos-notarization.zip xcrun notarytool submit pulse-agent-macos-notarization.zip \ --key AuthKey.p8 \ --key-id "$APPLE_NOTARY_KEY_ID" \ --issuer "$APPLE_NOTARY_ISSUER_ID" \ --wait \ --output-format json > notarization-result.json python3 - <<'PY' import json from pathlib import Path result = json.loads(Path('notarization-result.json').read_text()) if result.get('status') != 'Accepted': raise SystemExit(f"Apple notarization was not accepted: {result.get('status', 'unknown')}") PY for binary in native-agent-binaries/pulse-agent-darwin-*; do # Gatekeeper's spctl app assessment rejects bare command-line # Mach-O binaries even when the notary service accepted them. # Verify the signed bytes that will be packaged instead. codesign --verify --deep --strict --verbose=2 "$binary" done - name: Upload signed macOS binaries uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 with: name: signed-macos-agent-${{ github.sha }}-${{ inputs.version }} path: native-agent-binaries/ if-no-files-found: error retention-days: 1 compression-level: 0 sign-windows-agent: name: Authenticode Sign Windows Agent needs: signing-configuration if: ${{ inputs.require_windows_signing && needs.signing-configuration.result == 'success' }} runs-on: windows-2025 timeout-minutes: 50 permissions: actions: read contents: read outputs: signing_backend: ${{ steps.evidence.outputs.signing_backend }} steps: - name: Checkout repository uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3 - name: Set up Go uses: actions/setup-go@4a3601121dd01d1626a1e23e37211e3254c1c06c # v6.4.0 with: go-version-file: go.mod cache: true - name: Build unsigned agent binaries shell: pwsh env: PULSE_UPDATE_SIGNING_PUBLIC_KEY: ${{ vars.PULSE_UPDATE_SIGNING_PUBLIC_KEY }} run: | $ErrorActionPreference = 'Stop' foreach ($name in @('PULSE_UPDATE_SIGNING_PUBLIC_KEY')) { if ([string]::IsNullOrWhiteSpace((Get-Item "Env:$name").Value)) { throw "Missing required $name." } } New-Item -ItemType Directory -Path unsigned-native-agent-binaries -Force | Out-Null $ldflags = & bash ./scripts/release_ldflags.sh agent --version "v${{ inputs.version }}" --update-public-keys $env:PULSE_UPDATE_SIGNING_PUBLIC_KEY foreach ($arch in @('amd64', 'arm64', '386')) { $env:GOOS = 'windows' $env:GOARCH = $arch $env:CGO_ENABLED = '0' $output = "unsigned-native-agent-binaries/pulse-agent-windows-$arch.exe" go build -buildvcs=false -trimpath -ldflags="$ldflags" -o $output ./cmd/pulse-agent if ($LASTEXITCODE -ne 0) { throw "Go build failed for Windows $arch." } } - name: Sign with legacy PFX fallback if: ${{ inputs.windows_signing_backend == 'legacy-pfx' }} shell: pwsh env: WINDOWS_CERTIFICATE_PFX_BASE64: ${{ secrets.WINDOWS_CODE_SIGNING_CERTIFICATE_PFX_BASE64 }} WINDOWS_CERTIFICATE_PASSWORD: ${{ secrets.WINDOWS_CODE_SIGNING_CERTIFICATE_PASSWORD }} run: | $ErrorActionPreference = 'Stop' $pfxPath = Join-Path $env:RUNNER_TEMP 'pulse-code-signing.pfx' [IO.File]::WriteAllBytes($pfxPath, [Convert]::FromBase64String($env:WINDOWS_CERTIFICATE_PFX_BASE64)) $password = ConvertTo-SecureString $env:WINDOWS_CERTIFICATE_PASSWORD -AsPlainText -Force $certificate = Import-PfxCertificate -FilePath $pfxPath -CertStoreLocation Cert:\CurrentUser\My -Password $password -Exportable:$false if ($null -eq $certificate) { throw 'Failed to import Windows code-signing certificate.' } $signtool = Get-ChildItem "${env:ProgramFiles(x86)}\Windows Kits\10\bin" -Filter signtool.exe -Recurse | Sort-Object FullName -Descending | Select-Object -First 1 -ExpandProperty FullName foreach ($output in (Get-ChildItem unsigned-native-agent-binaries\*.exe)) { & $signtool sign /sha1 $certificate.Thumbprint /fd SHA256 /td SHA256 /tr http://timestamp.digicert.com $output.FullName if ($LASTEXITCODE -ne 0) { throw "Authenticode signing failed for $($output.Name)." } } - name: Upload unsigned SignPath input if: ${{ inputs.windows_signing_backend == 'signpath' }} id: upload-unsigned-windows uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 with: name: unsigned-windows-agent-${{ github.sha }}-${{ inputs.version }} path: unsigned-native-agent-binaries/ if-no-files-found: error retention-days: 1 compression-level: 0 - name: Submit SignPath Authenticode request if: ${{ inputs.windows_signing_backend == 'signpath' }} id: signpath uses: signpath/github-action-submit-signing-request@b9d91eadd323de506c0c81cf0c7fe7438f3360fd # v2 with: api-token: ${{ secrets.SIGNPATH_API_TOKEN }} organization-id: ${{ vars.SIGNPATH_ORGANIZATION_ID }} project-slug: ${{ vars.SIGNPATH_PROJECT_SLUG }} signing-policy-slug: ${{ vars.SIGNPATH_SIGNING_POLICY_SLUG }} artifact-configuration-slug: ${{ vars.SIGNPATH_ARTIFACT_CONFIGURATION_SLUG }} github-artifact-id: ${{ steps.upload-unsigned-windows.outputs.artifact-id }} github-token: ${{ secrets.GITHUB_TOKEN }} wait-for-completion: true wait-for-completion-timeout-in-seconds: 2400 output-artifact-directory: signed-native-agent-binaries parameters: | version: ${{ toJSON(inputs.version) }} - name: Verify Authenticode signatures and write evidence id: evidence shell: pwsh env: WINDOWS_SIGNING_BACKEND: ${{ inputs.windows_signing_backend }} SIGNPATH_SIGNING_REQUEST_ID: ${{ steps.signpath.outputs.signing-request-id }} SIGNPATH_SIGNING_REQUEST_URL: ${{ steps.signpath.outputs.signing-request-web-url }} SIGNPATH_INPUT_ARTIFACT_ID: ${{ steps.upload-unsigned-windows.outputs.artifact-id }} SIGNPATH_EXPECTED_CERTIFICATE_SUBJECT: ${{ vars.SIGNPATH_EXPECTED_CERTIFICATE_SUBJECT }} run: | $ErrorActionPreference = 'Stop' $sourceDir = if ($env:WINDOWS_SIGNING_BACKEND -eq 'signpath') { 'signed-native-agent-binaries' } else { 'unsigned-native-agent-binaries' } New-Item -ItemType Directory -Path native-agent-binaries -Force | Out-Null $signtool = Get-ChildItem "${env:ProgramFiles(x86)}\Windows Kits\10\bin" -Filter signtool.exe -Recurse | Sort-Object FullName -Descending | Select-Object -First 1 -ExpandProperty FullName $files = @() foreach ($arch in @('amd64', 'arm64', '386')) { $name = "pulse-agent-windows-$arch.exe" $source = Join-Path $sourceDir $name if (-not (Test-Path $source -PathType Leaf)) { throw "Signed output is missing $name." } & $signtool verify /pa /v $source if ($LASTEXITCODE -ne 0) { throw "Authenticode verification failed for $name." } $signature = Get-AuthenticodeSignature $source if ($signature.Status -ne 'Valid' -or $null -eq $signature.SignerCertificate) { throw "Invalid Authenticode status for $name: $($signature.Status)." } if ($env:WINDOWS_SIGNING_BACKEND -eq 'signpath' -and $signature.SignerCertificate.Subject -ne $env:SIGNPATH_EXPECTED_CERTIFICATE_SUBJECT) { throw "Unexpected Authenticode signer subject for ${name}: $($signature.SignerCertificate.Subject)." } Copy-Item $source (Join-Path native-agent-binaries $name) $files += [ordered]@{ name = $name sha256 = (Get-FileHash $source -Algorithm SHA256).Hash.ToLowerInvariant() signerSubject = $signature.SignerCertificate.Subject signerThumbprint = $signature.SignerCertificate.Thumbprint } } $evidence = [ordered]@{ schemaVersion = 1 backend = $env:WINDOWS_SIGNING_BACKEND version = '${{ inputs.version }}' sourceSha = $env:GITHUB_SHA workflowRunUrl = "https://github.com/$env:GITHUB_REPOSITORY/actions/runs/$env:GITHUB_RUN_ID" signPathSigningRequestId = $env:SIGNPATH_SIGNING_REQUEST_ID signPathSigningRequestUrl = $env:SIGNPATH_SIGNING_REQUEST_URL githubInputArtifactId = $env:SIGNPATH_INPUT_ARTIFACT_ID files = $files } $evidence | ConvertTo-Json -Depth 6 | Set-Content windows-signing-evidence.json -Encoding utf8NoBOM "signing_backend=$env:WINDOWS_SIGNING_BACKEND" | Out-File -FilePath $env:GITHUB_OUTPUT -Append -Encoding utf8 - name: Upload signed Windows binaries uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 with: name: signed-windows-agent-${{ github.sha }}-${{ inputs.version }} path: native-agent-binaries/ if-no-files-found: error retention-days: 1 compression-level: 0 - name: Upload Windows signing evidence uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 with: name: windows-signing-evidence-${{ github.sha }}-${{ inputs.version }} path: windows-signing-evidence.json if-no-files-found: error retention-days: 30 build: name: Build and Validate Release Candidate needs: [sign-macos-agent, sign-windows-agent] if: ${{ always() && (!inputs.require_macos_signing || needs.sign-macos-agent.result == 'success') && (!inputs.require_windows_signing || needs.sign-windows-agent.result == 'success') }} runs-on: ubuntu-24.04 timeout-minutes: 60 outputs: artifact_name: ${{ steps.identity.outputs.artifact_name }} manifest_artifact_name: ${{ steps.identity.outputs.manifest_artifact_name }} steps: - name: Resolve candidate identity id: identity run: | set -euo pipefail echo "artifact_name=release-candidate-${GITHUB_SHA}-${{ inputs.version }}" >> "$GITHUB_OUTPUT" echo "manifest_artifact_name=release-candidate-manifest-${GITHUB_SHA}-${{ inputs.version }}" >> "$GITHUB_OUTPUT" - name: Checkout repository uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3 with: fetch-depth: 0 - name: Validate candidate identity run: | set -euo pipefail test "$(tr -d '\n' < VERSION)" = "${{ inputs.version }}" test "$(git rev-parse HEAD)" = "${GITHUB_SHA}" - name: Set up Go uses: actions/setup-go@4a3601121dd01d1626a1e23e37211e3254c1c06c # v6.4.0 with: go-version-file: go.mod cache: true - name: Set up Node.js uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0 with: node-version: '20' cache: 'npm' cache-dependency-path: 'frontend-modern/package-lock.json' - name: Install release prerequisites run: | sudo apt-get update sudo apt-get install -y zip - name: Set up Helm uses: azure/setup-helm@dda3372f752e03dde6b3237bc9431cdc2f7a02a2 # v5.0.0 with: version: 'v3.15.2' - name: Install Syft run: | set -euo pipefail SYFT_VERSION="1.42.4" SYFT_ARCHIVE="syft_${SYFT_VERSION}_linux_amd64.tar.gz" SYFT_SHA256="590650c2743b83f327d1bf9bec64f6f83b7fec504187bb84f500c862bf8f2a0f" TMP_DIR="$(mktemp -d)" trap 'rm -rf "$TMP_DIR"' EXIT curl -fsSL "https://github.com/anchore/syft/releases/download/v${SYFT_VERSION}/${SYFT_ARCHIVE}" \ -o "${TMP_DIR}/${SYFT_ARCHIVE}" printf '%s %s\n' "${SYFT_SHA256}" "${TMP_DIR}/${SYFT_ARCHIVE}" | sha256sum --check -- tar -xzf "${TMP_DIR}/${SYFT_ARCHIVE}" -C "${TMP_DIR}" syft install -m 0755 "${TMP_DIR}/syft" /usr/local/bin/syft - name: Download signed macOS binaries if: ${{ inputs.require_macos_signing }} uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4.3.0 with: name: signed-macos-agent-${{ github.sha }}-${{ inputs.version }} path: native-agent-binaries - name: Download signed Windows binaries if: ${{ inputs.require_windows_signing }} uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4.3.0 with: name: signed-windows-agent-${{ github.sha }}-${{ inputs.version }} path: native-agent-binaries - name: Download Windows signing evidence if: ${{ inputs.require_windows_signing }} uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4.3.0 with: name: windows-signing-evidence-${{ github.sha }}-${{ inputs.version }} path: release-candidate-manifest - name: Build release candidate run: ./scripts/build-release.sh "${{ inputs.version }}" env: PULSE_LICENSE_PUBLIC_KEY: ${{ secrets.PULSE_LICENSE_PUBLIC_KEY }} PULSE_UPDATE_SIGNING_KEY: ${{ secrets.PULSE_UPDATE_SIGNING_KEY }} PULSE_UPDATE_SIGNING_PUBLIC_KEY: ${{ vars.PULSE_UPDATE_SIGNING_PUBLIC_KEY }} PULSE_REQUIRE_MACOS_SIGNING: ${{ inputs.require_macos_signing }} PULSE_REQUIRE_WINDOWS_SIGNING: ${{ inputs.require_windows_signing }} PULSE_AGENT_NATIVE_BINARIES_DIR: ${{ (inputs.require_macos_signing || inputs.require_windows_signing) && format('{0}/native-agent-binaries', github.workspace) || '' }} - name: Validate installer signing key pins env: PULSE_UPDATE_SIGNING_PUBLIC_KEY: ${{ vars.PULSE_UPDATE_SIGNING_PUBLIC_KEY }} run: | set -euo pipefail TRUSTED_SSH_PUBLIC_KEY="$( go run ./scripts/release_update_key.go public-key-ssh \ --public-key "${PULSE_UPDATE_SIGNING_PUBLIC_KEY}" \ --comment pulse-installer )" for installer in install.sh scripts/pulse-auto-update.sh release/pulse-auto-update.sh; do grep -F "PINNED_RELEASE_SSH_PUBLIC_KEY=\"${TRUSTED_SSH_PUBLIC_KEY}\"" "${installer}" >/dev/null || { echo "::error::${installer} does not trust the configured release signing key." exit 1 } done - name: Validate complete candidate locally run: ./scripts/validate-release.sh "${{ inputs.version }}" --skip-docker - name: Create immutable candidate manifest run: | python3 scripts/release_candidate_manifest.py create \ --release-dir release \ --version "${{ inputs.version }}" \ --source-sha "${GITHUB_SHA}" \ --output release-candidate-manifest/release-candidate.json - name: Upload immutable release candidate uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 with: name: ${{ steps.identity.outputs.artifact_name }} path: release/ if-no-files-found: error retention-days: 1 compression-level: 0 overwrite: true - name: Upload candidate manifest uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 with: name: ${{ steps.identity.outputs.manifest_artifact_name }} path: release-candidate-manifest/ if-no-files-found: error retention-days: 1 overwrite: true