From 0e52dbd7d52e2bb7e339a9b80c5424ecee592dde Mon Sep 17 00:00:00 2001 From: chen21019 <19357113+chen21019@users.noreply.github.com> Date: Sun, 25 Oct 2026 06:41:33 +0800 Subject: [PATCH 1/3] Pin shared Ubuntu security backports for v0.8.21 Integrate only existing runtime packages and preserve raw scans, native OpenVEX, producer checksum verification and strict vulnerability policy. Shared OCI producer: 59f4ec1f31e510361c4635023c08fcf2a2b2133c. OCI completion and final-image gates remain pending; this source commit does not publish a release. --- .github/workflows/release.yml | 41 +++++++++++++++++++-- .github/workflows/supply-chain.yml | 57 +++++++++++++++++++++++++----- README.md | 40 +++++---------------- package/Dockerfile | 24 +++++++++++++ 4 files changed, 119 insertions(+), 43 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 47d92b6..9cbca66 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -87,15 +87,47 @@ jobs: --scanners vuln,secret --severity UNKNOWN,LOW,MEDIUM,HIGH,CRITICAL \ --format json \ --output "/release/resource-scheduler-${RELEASE_TAG#v}-security.json" "$RUNTIME_IMAGE" - bash scripts/assess-runtime-vulnerabilities \ - "$release_dir/resource-scheduler-${RELEASE_TAG#v}-security.json" \ - "$release_dir" "$RUNTIME_IMAGE" docker run --rm --network none \ -v /var/run/docker.sock:/var/run/docker.sock \ -v "$PWD/$release_dir:/release" \ -v "$cache:/root/.cache/trivy" \ "$TRIVY_IMAGE" image --skip-db-update --offline-scan --format cyclonedx \ --output "/release/resource-scheduler-${RELEASE_TAG#v}-image.cdx.json" "$RUNTIME_IMAGE" + mkdir -p "$release_dir" + consumer_container=$(docker create "$RUNTIME_IMAGE" /bin/true) + if docker cp "$consumer_container:/usr/share/pasturestack/security/ubuntu-backports" "$release_dir"/ubuntu-backports; then + docker rm "$consumer_container" >/dev/null + else + docker rm "$consumer_container" >/dev/null + exit 1 + fi + (cd "$release_dir"/ubuntu-backports && sha256sum --check SHA256SUMS) + bundle_image='ghcr.io/pasturestack/ubuntu-security-backports:v0.1.0@sha256:2742a3d429e4e14bc587f6dedc51ded4246cf90bbaedcd86c67d0885e32a6f2a' + mkdir -p "$release_dir"/bundle-tools + bundle_container=$(docker create "$bundle_image" unused) + if docker cp "$bundle_container:/out/verify-consumer-evidence.py" "$release_dir"/bundle-tools/verify-consumer-evidence.py && \ + docker cp "$bundle_container:/out/SHA256SUMS" "$release_dir"/bundle-tools/SHA256SUMS; then + docker rm "$bundle_container" >/dev/null + else + docker rm "$bundle_container" >/dev/null + exit 1 + fi + (cd "$release_dir"/bundle-tools && \ + test "$(grep -Ec '^[0-9a-f]{64} verify-consumer-evidence\.py$' SHA256SUMS)" -eq 1 && \ + grep -E '^[0-9a-f]{64} verify-consumer-evidence\.py$' SHA256SUMS | sha256sum --check -) + python3 "$release_dir"/bundle-tools/verify-consumer-evidence.py \ + --evidence-dir "$release_dir"/ubuntu-backports --sbom "$release_dir/resource-scheduler-${RELEASE_TAG#v}-image.cdx.json" \ + --bundle-checksums "$release_dir"/bundle-tools/SHA256SUMS \ + --expected-bundle-digest 'sha256:2742a3d429e4e14bc587f6dedc51ded4246cf90bbaedcd86c67d0885e32a6f2a' > "$release_dir"/ubuntu-backports-validation.json + docker run --rm --network none -v /var/run/docker.sock:/var/run/docker.sock \ + -v "$PWD/$release_dir:/release" -v "$cache:/root/.cache/trivy" "$TRIVY_IMAGE" \ + image --skip-db-update --offline-scan --scanners vuln,secret \ + --severity UNKNOWN,LOW,MEDIUM,HIGH,CRITICAL --vex /release/ubuntu-backports/openvex.json \ + --format json --output "/release/resource-scheduler-${RELEASE_TAG#v}-security.effective.json" "$RUNTIME_IMAGE" + bash scripts/assess-runtime-vulnerabilities \ + "$release_dir/resource-scheduler-${RELEASE_TAG#v}-security.effective.json" \ + "$release_dir" "$RUNTIME_IMAGE" + tar -cf "$release_dir/ubuntu-backports-evidence.tar" -C "$release_dir" ubuntu-backports ubuntu-backports-validation.json docker run --rm --entrypoint cat "$RUNTIME_IMAGE" \ /usr/share/pasturestack/manifests/runtime-ubuntu-packages.tsv \ > "$release_dir/resource-scheduler-${RELEASE_TAG#v}-ubuntu-packages.tsv" @@ -113,6 +145,7 @@ jobs: "resource-scheduler-${RELEASE_TAG#v}-linux-amd64.tar.xz" \ "resource-scheduler-${RELEASE_TAG#v}-image.cdx.json" \ "resource-scheduler-${RELEASE_TAG#v}-security.json" \ + "resource-scheduler-${RELEASE_TAG#v}-security.effective.json" ubuntu-backports-evidence.tar \ "resource-scheduler-${RELEASE_TAG#v}-ubuntu-packages.tsv" \ runtime-risk-summary.txt \ runtime-vendor-unfixed.tsv \ @@ -158,6 +191,8 @@ jobs: "dist/release/resource-scheduler-${RELEASE_TAG#v}-linux-amd64.tar.xz" \ "dist/release/resource-scheduler-${RELEASE_TAG#v}-image.cdx.json" \ "dist/release/resource-scheduler-${RELEASE_TAG#v}-security.json" \ + "dist/release/resource-scheduler-${RELEASE_TAG#v}-security.effective.json" \ + dist/release/ubuntu-backports-evidence.tar \ "dist/release/resource-scheduler-${RELEASE_TAG#v}-ubuntu-packages.tsv" \ dist/release/runtime-risk-summary.txt \ dist/release/runtime-vendor-unfixed.tsv \ diff --git a/.github/workflows/supply-chain.yml b/.github/workflows/supply-chain.yml index 31cfe83..535f53d 100644 --- a/.github/workflows/supply-chain.yml +++ b/.github/workflows/supply-chain.yml @@ -22,7 +22,7 @@ jobs: runs-on: ubuntu-24.04 timeout-minutes: 90 env: - CANDIDATE_VERSION: v0.8.20 + CANDIDATE_VERSION: v0.8.21 DAPPER_IMAGE: local/resource-scheduler-dapper:${{ github.sha }} TRIVY_IMAGE: aquasec/trivy:0.74.0@sha256:62b1e65e8869bc4b4c6aa4fa2b21595256c7c2f6018a9d9ad61caf87187c1969 @@ -272,18 +272,58 @@ jobs: --output "/evidence/${scope}.cdx.json" "$image" done + - name: Validate qualified backports and apply native runtime OpenVEX + shell: bash + run: | + set -euo pipefail + mkdir -p evidence + consumer_container=$(docker create "$RUNTIME_IMAGE" /bin/true) + if docker cp "$consumer_container:/usr/share/pasturestack/security/ubuntu-backports" evidence/ubuntu-backports; then + docker rm "$consumer_container" >/dev/null + else + docker rm "$consumer_container" >/dev/null + exit 1 + fi + (cd evidence/ubuntu-backports && sha256sum --check SHA256SUMS) + bundle_image='ghcr.io/pasturestack/ubuntu-security-backports:v0.1.0@sha256:2742a3d429e4e14bc587f6dedc51ded4246cf90bbaedcd86c67d0885e32a6f2a' + mkdir -p evidence/bundle-tools + bundle_container=$(docker create "$bundle_image" unused) + if docker cp "$bundle_container:/out/verify-consumer-evidence.py" evidence/bundle-tools/verify-consumer-evidence.py && \ + docker cp "$bundle_container:/out/SHA256SUMS" evidence/bundle-tools/SHA256SUMS; then + docker rm "$bundle_container" >/dev/null + else + docker rm "$bundle_container" >/dev/null + exit 1 + fi + (cd evidence/bundle-tools && \ + test "$(grep -Ec '^[0-9a-f]{64} verify-consumer-evidence\.py$' SHA256SUMS)" -eq 1 && \ + grep -E '^[0-9a-f]{64} verify-consumer-evidence\.py$' SHA256SUMS | sha256sum --check -) + python3 evidence/bundle-tools/verify-consumer-evidence.py \ + --evidence-dir evidence/ubuntu-backports --sbom evidence/runtime.cdx.json \ + --bundle-checksums evidence/bundle-tools/SHA256SUMS \ + --expected-bundle-digest 'sha256:2742a3d429e4e14bc587f6dedc51ded4246cf90bbaedcd86c67d0885e32a6f2a' > evidence/ubuntu-backports-validation.json + trivy_cache="$RUNNER_TEMP/resource-scheduler-trivy-cache" + docker run --rm --network none --volume /var/run/docker.sock:/var/run/docker.sock \ + --volume "$PWD/evidence:/evidence" --volume "$trivy_cache:/root/.cache/trivy" \ + "$TRIVY_IMAGE" image --skip-db-update --offline-scan --scanners vuln,secret \ + --severity UNKNOWN,LOW,MEDIUM,HIGH,CRITICAL --vex /evidence/ubuntu-backports/openvex.json \ + --format json --output /evidence/runtime-security.effective.json "$RUNTIME_IMAGE" + - name: Enforce runtime and builder security boundaries shell: bash run: | set -euo pipefail : > evidence/security-summary.txt + jq '{critical: [.Results[]?.Vulnerabilities[]? | select(.Severity == "CRITICAL")] | length, + high: [.Results[]?.Vulnerabilities[]? | select(.Severity == "HIGH")] | length}' \ + evidence/runtime-security.json > evidence/runtime-raw-severity-counts.json source_secrets="$(jq '[.Results[]?.Secrets[]?] | length' evidence/source-security.json)" - runtime_secrets="$(jq '[.Results[]?.Secrets[]?] | length' evidence/runtime-security.json)" - runtime_critical="$(jq '[.Results[]?.Vulnerabilities[]? | select(.Severity == "CRITICAL")] | length' evidence/runtime-security.json)" - runtime_high="$(jq '[.Results[]?.Vulnerabilities[]? | select(.Severity == "HIGH")] | length' evidence/runtime-security.json)" - runtime_vulnerabilities="$(jq '[.Results[]?.Vulnerabilities[]?] | length' evidence/runtime-security.json)" + runtime_secrets="$(jq '[.Results[]?.Secrets[]?] | length' evidence/runtime-security.effective.json)" + runtime_critical="$(jq '[.Results[]?.Vulnerabilities[]? | select(.Severity == "CRITICAL")] | length' evidence/runtime-security.effective.json)" + runtime_high="$(jq '[.Results[]?.Vulnerabilities[]? | select(.Severity == "HIGH")] | length' evidence/runtime-security.effective.json)" + runtime_vulnerabilities="$(jq '[.Results[]?.Vulnerabilities[]?] | length' evidence/runtime-security.effective.json)" bash scripts/assess-runtime-vulnerabilities \ - evidence/runtime-security.json evidence "$RUNTIME_IMAGE" + evidence/runtime-security.effective.json evidence "$RUNTIME_IMAGE" runtime_vendor_unfixed="$(sed -n 's/^runtime_vendor_unfixed_low_medium=//p' evidence/runtime-risk-summary.txt)" runtime_actionable="$(sed -n 's/^runtime_actionable=//p' evidence/runtime-risk-summary.txt)" dapper_secrets="$(jq '[.Results[]?.Secrets[]?] | length' evidence/dapper-security.json)" @@ -329,9 +369,10 @@ jobs: mkdir -p dist/security-runtime docker image inspect "$RUNTIME_IMAGE" > dist/security-runtime/images.json docker save "$RUNTIME_IMAGE" | gzip -n > dist/security-runtime/images.tar.gz - cp evidence/runtime-security.json evidence/runtime.cdx.json dist/security-runtime/ + cp evidence/runtime-security.json evidence/runtime-security.effective.json evidence/runtime.cdx.json dist/security-runtime/ + tar -cf dist/security-runtime/ubuntu-backports-evidence.tar -C evidence ubuntu-backports ubuntu-backports-validation.json printf '%s\n' "$GITHUB_SHA" > dist/security-runtime/source-revision.txt - (cd dist/security-runtime && sha256sum -- *.json images.tar.gz source-revision.txt > SHA256SUMS) + (cd dist/security-runtime && sha256sum -- *.json *.tar images.tar.gz source-revision.txt > SHA256SUMS) - name: Retain verified runtime for immutable publication uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 with: diff --git a/README.md b/README.md index 3775549..3d854be 100644 --- a/README.md +++ b/README.md @@ -1,11 +1,8 @@ # PastureStack Resource Scheduler -The current published release is [v0.8.20](https://github.com/PastureStack/resource-scheduler/releases/tag/v0.8.20), -produced from `35d3a67c62df4b22d7167da3d73dd9dbc4905e66` by the existing -[release workflow](https://github.com/PastureStack/resource-scheduler/actions/runs/39961862641). -Its runtime digest is `sha256:e5a389273344585fca5c4513060e5289b30ca13046d8ec286b8563952f1325d4`. -Only `release.yml` publishes; `supply-chain.yml` retains verification evidence -without a second publisher. Later source changes do not replace released bytes. +Linux candidate **`v0.8.21`** integrates shared Ubuntu 26.04 security backports for already-installed packages while preserving the selected coreutils provider. It is unpublished; the last published Linux release is [v0.8.20](https://github.com/PastureStack/resource-scheduler/releases/tag/v0.8.20). + +Build and image examples below target the candidate. Publication requires completed bundle provenance and the normal final-image release gates. The image retains bundle manifests, source inputs and install/verify receipts under `/usr/share/pasturestack/security/ubuntu-backports/`. Resource Scheduler is the control-plane daemon that tracks host capacity, labels, and host-port reservations. It consumes the dated metadata API, handles scheduling events, ranks eligible hosts, reserves and releases resources, and exposes the health endpoint used by the infrastructure catalog. @@ -13,19 +10,7 @@ PastureStack is an independent community effort to preserve, audit, and moderniz **Upstream:** [`rancher/scheduler`](https://github.com/rancher/scheduler). This GitHub fork retains the upstream Git history, authorship, dates, and license notices. PastureStack maintenance is consolidated into one commit after the preserved upstream boundary. -The current published release is [`v0.8.20`](https://github.com/PastureStack/resource-scheduler/releases/tag/v0.8.20). -The corresponding Catalog version `5` is recorded in the -[Catalog update](https://github.com/PastureStack/catalog-templates/pull/18). - -Release `v0.8.20` refreshes the official Go security release and Ubuntu packages. -Its actual runtime scan has 0 High/Critical findings, 0 vendor-fixed findings, -and 0 secrets. The 4 Low and 47 Medium vendor-unfixed findings remain -`under_investigation` in the release risk register and OpenVEX; they are not -resolved. Release checksums and the SBOM and image build-provenance attestations -were verified against the exact signed tag, source commit, and release workflow. -Publication does not establish a managed-system upgrade or rollback result. - -Its disposable builder also selects the official `golang.org/x/net` 0.60.0, +The disposable builder also selects the official `golang.org/x/net` 0.60.0, OpenTelemetry-Go 1.45.0, and containerd 2.3.6 security updates for Docker CLI and Buildx. These tools are not copied into the runtime image. @@ -37,20 +22,12 @@ vendor-fixed builder finding remain blocking. This does not assess the host kernel; vendor-unfixed Low/Medium and unrated builder findings retain their original severity and `under_investigation` status. -The [native evidence run](https://github.com/PastureStack/resource-scheduler/actions/runs/39357570483) -retains the raw builder scan: 8 Critical and 192 High header source-package -matches are separately classified by 200 exact `not_affected` statements. -Another 2,600 vendor-unfixed package findings remain recorded, including the -unrated `GO-2026-5932` module advisory. The actual Buildx compilation graph -contains no `golang.org/x/crypto/openpgp` packages; that fact does not remove -the module finding or assess any host kernel. - ## Runtime image -The maintained image is published from this repository as: +After publication, the candidate image coordinate is: ```text -ghcr.io/pasturestack/resource-scheduler: +ghcr.io/pasturestack/resource-scheduler:v0.8.21 ``` This image is a system component, not a standalone application. The PastureStack infrastructure catalog supplies the control-plane URL, scoped access credentials, metadata endpoint, health-check configuration, and an explicit numeric version tag. The container runs as UID/GID `10001:10001`; only the scheduler binary receives `cap_net_bind_service` so the established port `80` health contract can remain available without a root process. @@ -71,7 +48,7 @@ make validate bash scripts/check-build-downloads go list -mod=vendor ./... bash scripts/check-migration-policy -VERSION_OVERRIDE=v0.8.20 IMAGE_NAMESPACE=pasturestack make package +VERSION_OVERRIDE=v0.8.21 IMAGE_NAMESPACE=pasturestack make package ``` CI validates source and dependency locks, tests and reproducible builds, and generates short-lived security evidence. Releases are published only from an annotated, pure numeric SemVer tag that resolves to the reviewed commit. @@ -101,5 +78,4 @@ finding with an official fix blocks release, regardless of severity. Unknown findings block unless a separately reviewed, exact not-affected statement is proved by the actual product dependency graph. LOW/MEDIUM findings without an official fix require the repository's exact CVE/package/version/PURL review ledger; -new or changed findings fail closed. This policy-only update does not replace any -already published immutable artifact. +new or changed findings fail closed. Each candidate must pass this policy against its exact image and package versions. diff --git a/package/Dockerfile b/package/Dockerfile index 2bf28c4..8ef889a 100644 --- a/package/Dockerfile +++ b/package/Dockerfile @@ -1,5 +1,7 @@ +# syntax=docker/dockerfile:1.7 ARG SOURCE_DATE_EPOCH ARG UBUNTU_IMAGE=ubuntu:26.04@sha256:2260313b31c8c011cd2eebe728008efac1b3982be73eb71348ea2648d2c0e09b +FROM ghcr.io/pasturestack/ubuntu-security-backports:v0.1.0@sha256:2742a3d429e4e14bc587f6dedc51ded4246cf90bbaedcd86c67d0885e32a6f2a AS ubuntu_security_backports FROM ${UBUNTU_IMAGE} AS snapshot-ca-bootstrap ADD --checksum=sha256:5b6326f1932cfd7be57130561dc78192f09ed9f58bfbe6606ed0c50510c3b6cd \ https://archive.ubuntu.com/ubuntu/pool/main/c/ca-certificates/ca-certificates_20260816ubuntu0.26.04.1_all.deb \ @@ -79,6 +81,28 @@ RUN set -eux; \ rm -f /var/log/lastlog /var/log/faillog ENV HOME=/home/pasturestack +# Install qualified backports only for packages already in this runtime. +# The immutable read-only build mount adds no installer or interpreter to it. +RUN --mount=from=ubuntu_security_backports,source=/out,target=/opt/pasturestack/ubuntu-security-backports,ro \ + set -eux; \ + evidence=/usr/share/pasturestack/security/ubuntu-backports; \ + mkdir -p "${evidence}"; \ + /opt/pasturestack/ubuntu-security-backports/install-existing-backports --install-in-docker-build > "${evidence}/install.json"; \ + /opt/pasturestack/ubuntu-security-backports/install-existing-backports --verify-installed > "${evidence}/verify.json"; \ + for document in manifest.json qualification.json openvex.json source-inputs.lock.json; do \ + cp "/opt/pasturestack/ubuntu-security-backports/${document}" "${evidence}/${document}"; \ + done; \ + printf '%s\n' 'ghcr.io/pasturestack/ubuntu-security-backports:v0.1.0@sha256:2742a3d429e4e14bc587f6dedc51ded4246cf90bbaedcd86c67d0885e32a6f2a' > "${evidence}/bundle-image.txt"; \ + (cd "${evidence}" && sha256sum manifest.json qualification.json openvex.json source-inputs.lock.json install.json verify.json bundle-image.txt > SHA256SUMS); \ + dpkg-query -W -f='${binary:Package}\t${Version}\n' | LC_ALL=C sort > /usr/share/pasturestack/ubuntu-security-packages.tsv; \ + { \ + printf 'record\tname\tarchitecture\tversion\n'; \ + printf 'metadata\tschema\t-\tpasturestack.dpkg-manifest/v1\n'; \ + printf 'metadata\tubuntu_snapshot\t-\t%s\n' "${UBUNTU_SNAPSHOT}"; \ + dpkg-query -W -f='package\t${binary:Package}\t${Architecture}\t${Version}\n' | LC_ALL=C sort; \ + } > /usr/share/pasturestack/manifests/runtime-ubuntu-packages.tsv; \ + rm -f /var/cache/ldconfig/aux-cache + USER 10001:10001 LABEL org.opencontainers.image.title="PastureStack Resource Scheduler" \ From 16537f7b00e126c2da1f2ff60958fbe0a9bd5fa5 Mon Sep 17 00:00:00 2001 From: chen21019 <19357113+chen21019@users.noreply.github.com> Date: Sun, 25 Oct 2026 06:56:44 +0800 Subject: [PATCH 2/3] Align runtime README for v0.8.21 README only. Preserve runtime and verification source exactly from 0e52dbd7d52e2bb7e339a9b80c5424ecee592dde. Describe the release version without obsolete candidate or previous-release notes. --- README.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 3d854be..d08ccfc 100644 --- a/README.md +++ b/README.md @@ -1,8 +1,8 @@ # PastureStack Resource Scheduler -Linux candidate **`v0.8.21`** integrates shared Ubuntu 26.04 security backports for already-installed packages while preserving the selected coreutils provider. It is unpublished; the last published Linux release is [v0.8.20](https://github.com/PastureStack/resource-scheduler/releases/tag/v0.8.20). +Linux runtime **`v0.8.21`** integrates shared Ubuntu 26.04 security backports for already-installed packages while preserving the selected coreutils provider. -Build and image examples below target the candidate. Publication requires completed bundle provenance and the normal final-image release gates. The image retains bundle manifests, source inputs and install/verify receipts under `/usr/share/pasturestack/security/ubuntu-backports/`. +The image retains bundle manifests, source inputs and installation receipts under `/usr/share/pasturestack/security/ubuntu-backports/`. Resource Scheduler is the control-plane daemon that tracks host capacity, labels, and host-port reservations. It consumes the dated metadata API, handles scheduling events, ranks eligible hosts, reserves and releases resources, and exposes the health endpoint used by the infrastructure catalog. @@ -24,7 +24,7 @@ original severity and `under_investigation` status. ## Runtime image -After publication, the candidate image coordinate is: +The Linux image coordinate is: ```text ghcr.io/pasturestack/resource-scheduler:v0.8.21 @@ -78,4 +78,4 @@ finding with an official fix blocks release, regardless of severity. Unknown findings block unless a separately reviewed, exact not-affected statement is proved by the actual product dependency graph. LOW/MEDIUM findings without an official fix require the repository's exact CVE/package/version/PURL review ledger; -new or changed findings fail closed. Each candidate must pass this policy against its exact image and package versions. +new or changed findings fail closed. Each release must pass this policy against its exact image and package versions. From cdfded3ca9bf6b6467595100e850baba58b7377b Mon Sep 17 00:00:00 2001 From: chen21019 <19357113+chen21019@users.noreply.github.com> Date: Sun, 25 Oct 2026 07:06:31 +0800 Subject: [PATCH 3/3] Align migration release assertion with v0.8.21 --- scripts/check-migration-policy | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/check-migration-policy b/scripts/check-migration-policy index b818173..7b13ea8 100755 --- a/scripts/check-migration-policy +++ b/scripts/check-migration-policy @@ -43,7 +43,7 @@ done grep -Fq "PastureStack is an independent community effort to preserve, audit, and modernize the ${legacy_title} 1.6 ecosystem. It is not affiliated with or endorsed by ${legacy_title} Labs or SUSE." README.md || fail "independence_notice_missing" grep -Fq 'ad28c1d94b7f1b1814fa6404886398b4f4094d64' ORIGIN.md || fail "upstream_boundary_missing" grep -Fq 'ghcr.io/pasturestack/resource-scheduler' README.md || fail "public_image_missing" -grep -Fq 'v0.8.20' README.md || fail "current_numeric_release_missing" +grep -Fq 'v0.8.21' README.md || fail "current_numeric_release_missing" grep -Fq "[[ \"\$RELEASE_TAG\" =~ ^v[0-9]+\\.[0-9]+\\.[0-9]+\$ ]]" .github/workflows/release.yml || fail "numeric_release_gate_missing" if git grep -I -n -E "${legacy_brand}-1\\.6-|${retired_short}" -- ':!vendor/**' ':!LICENSES/**' >/tmp/resource-scheduler-policy-matches 2>/dev/null; then