diff --git a/.github/requirements-ci.txt b/.github/requirements-ci.txt index 237054009..6ee7200b6 100644 --- a/.github/requirements-ci.txt +++ b/.github/requirements-ci.txt @@ -1 +1,2 @@ pre-commit==4.6.2 +PyYAML==6.0.3 diff --git a/.github/workflows/bonedigger.yml b/.github/workflows/bonedigger.yml index 726c8af13..65254246c 100644 --- a/.github/workflows/bonedigger.yml +++ b/.github/workflows/bonedigger.yml @@ -1,14 +1,21 @@ name: issue lifecycle +# Triggers are deliberately narrower than they look like they could be. The +# pinned bonedigger lifecycle has exactly two jobs, `issues.opened` and +# `issue_comment.created`, so subscribing to any other activity type dispatches +# the reusable workflow only to skip every job in it (bluefin#981). Widen this +# only alongside a bonedigger release that actually handles the new type. on: issues: - types: [opened, labeled, closed] + types: [opened] issue_comment: types: [created] +# Scoped to what the callee declares (issues: write, contents: read). It reads +# `event.issue.pull_request` straight from the payload and never calls the pulls +# API, so no pull-requests scope is needed. permissions: issues: write - pull-requests: write contents: read jobs: diff --git a/.github/workflows/lab-check.yml b/.github/workflows/lab-check.yml index 885174c24..bd6e8b012 100644 --- a/.github/workflows/lab-check.yml +++ b/.github/workflows/lab-check.yml @@ -33,7 +33,7 @@ jobs: - name: Fail with actionable message if token minting was denied if: steps.app-token.outcome == 'failure' || steps.app-token.outputs.token == '' run: | - echo "::error::MergeRaptor token mint failed — the GitHub App installation almost certainly lacks the requested 'checks: write' permission. This cannot be fixed from the repository; an org owner must grant it (Organization settings → GitHub Apps → MergeRaptor → Permissions). Verify current grants with: gh api orgs/projectbluefin/installations --jq '.installations[] | select(.app_slug == \"mergeraptor\") | .permissions' — see docs/skills/ci/references/mergeraptor-checks.md." + echo "::error::MergeRaptor token mint failed — the app lacks the requested 'checks: write' permission. This cannot be fixed from the repository; an org owner must grant it in the GitHub UI, and it may take TWO steps. Step 1, only if 'checks' is absent from 'gh api /apps/mergeraptor --jq .permissions': Organization settings → Developer settings → GitHub Apps → MergeRaptor → Permissions & events → Repository permissions → Checks: Read and write → Save. Until that is done the installation page has nothing to approve. Step 2: approve the resulting request under Organization settings → GitHub Apps → MergeRaptor → Review request. Verify with: gh api orgs/projectbluefin/installations --jq '.installations[] | select(.app_slug == \"mergeraptor\") | .permissions' — see docs/skills/ci/references/mergeraptor-checks.md." exit 1 - name: Create or update lab check @@ -82,9 +82,15 @@ jobs: STARTED_AT=$(jq -r '.started_at // empty' <<<"${CHECK_JSON}") COMPLETED_AT=$(jq -r '.completed_at // empty' <<<"${CHECK_JSON}") + # check_name filters server-side. Without it the first page is 100 + # check runs of every name, newest first, so on a busy commit the + # existing lab check falls off the page and this lookup returns + # nothing -- which silently turns every update into a duplicate + # check run (bluefin#939). CHECK_ID=$( gh api --method GET \ "repos/${GITHUB_REPOSITORY}/commits/${SHA}/check-runs" \ + -f check_name="${CHECK_NAME}" \ -f per_page=100 \ --jq '.check_runs | map(select(.name == "'"${CHECK_NAME}"'" and .app.slug == "'"${APP_SLUG}"'")) diff --git a/.github/workflows/pr-validation.yml b/.github/workflows/pr-validation.yml index a42e488f5..c82299e2b 100644 --- a/.github/workflows/pr-validation.yml +++ b/.github/workflows/pr-validation.yml @@ -50,6 +50,9 @@ jobs: - name: Check testsuite workflow contract run: python3 scripts/check-testsuite-workflow-ref.py + - name: Check Renovate auto-merge authentication contract + run: python3 scripts/check-renovate-automerge-auth.py + - name: Check for undeclared gitlinks shell: bash env: @@ -192,7 +195,12 @@ jobs: run: | set -euo pipefail # Minimum line coverage threshold (raise this as coverage improves). - THRESHOLD=30 + # Measured BATS line coverage has held steady at 53.8-54.4% across + # recent pr-validation runs (e.g. runs 31344003528, 31345539779, + # 31715546591, 31742984919). 45% keeps a buffer against normal + # run-to-run variance while still catching real regressions; see + # https://github.com/projectbluefin/bluefin/issues/518. + THRESHOLD=45 COBERTURA="coverage/kcov/cobertura.xml" if [[ ! -f "${COBERTURA}" ]]; then echo "warning: ${COBERTURA} not found; skipping threshold check" >&2 diff --git a/.github/workflows/promote-testing-to-main.yml b/.github/workflows/promote-testing-to-main.yml index 16dffde0a..23f9da624 100644 --- a/.github/workflows/promote-testing-to-main.yml +++ b/.github/workflows/promote-testing-to-main.yml @@ -21,7 +21,43 @@ permissions: statuses: write jobs: + release_window: + name: Check stable release window + runs-on: ubuntu-latest + timeout-minutes: 5 + outputs: + should_enqueue: ${{ steps.window.outputs.should_enqueue }} + steps: + - name: Determine whether to enqueue the promotion + id: window + env: + EVENT_NAME: ${{ github.event_name }} + run: | + set -euo pipefail + + case "$EVENT_NAME" in + workflow_dispatch) + echo "Manual dispatch — allowing the hotfix release escape hatch." + echo "should_enqueue=true" >> "$GITHUB_OUTPUT" + ;; + schedule) + weekday=$(date -u +%u) + if [[ "$weekday" == "2" ]]; then + echo "Tuesday UTC schedule — allowing the weekly stable release." + echo "should_enqueue=true" >> "$GITHUB_OUTPUT" + else + echo "Non-Tuesday schedule — refreshing promotion gates without enqueueing." + echo "should_enqueue=false" >> "$GITHUB_OUTPUT" + fi + ;; + *) + echo "Push trigger — refreshing the promotion PR without enqueueing." + echo "should_enqueue=false" >> "$GITHUB_OUTPUT" + ;; + esac + promote: + needs: [release_window] uses: projectbluefin/actions/.github/workflows/reusable-promote-squash.yml@v1 with: variants: >- @@ -31,5 +67,5 @@ jobs: run_e2e: false # main uses a merge queue ruleset (17070404): gh pr merge --auto is blocked. # use_merge_queue: true calls enqueuePullRequest GraphQL instead. - use_merge_queue: true + use_merge_queue: ${{ needs.release_window.outputs.should_enqueue == 'true' }} secrets: inherit diff --git a/.github/workflows/renovate-automerge.yml b/.github/workflows/renovate-automerge.yml index ebc9bd5dd..c12101041 100644 --- a/.github/workflows/renovate-automerge.yml +++ b/.github/workflows/renovate-automerge.yml @@ -15,3 +15,6 @@ jobs: uses: projectbluefin/actions/.github/workflows/reusable-renovate-automerge.yml@v1 with: head_sha: ${{ github.event.workflow_run.head_sha }} + secrets: + app_id: ${{ secrets.MERGERAPTOR_APP_ID }} + private_key: ${{ secrets.MERGERAPTOR_PRIVATE_KEY }} diff --git a/.github/workflows/scorecard.yml b/.github/workflows/scorecard.yml index dfb769e55..eea607b06 100644 --- a/.github/workflows/scorecard.yml +++ b/.github/workflows/scorecard.yml @@ -70,6 +70,6 @@ jobs: # Upload the results to GitHub's code scanning dashboard (optional). # Commenting out will disable upload of results to your repo's Code Scanning dashboard - name: "Upload to code-scanning" - uses: github/codeql-action/upload-sarif@ff2f1c621b7f889edc0d3c761ac2e6a3f8cdb0dd # v4 + uses: github/codeql-action/upload-sarif@cdf488f595d80d6e07e03d4674febd5ab45fa938 # v4 with: sarif_file: results.sarif diff --git a/Containerfile b/Containerfile index 4dfdebb17..98ced21f5 100644 --- a/Containerfile +++ b/Containerfile @@ -35,6 +35,7 @@ COPY /build_files/shared/utils/ghcurl /build_files/shared/utils/ghcurl FROM scratch AS ctx COPY /system_files /system_files COPY /build_files/shared/build-gnome-extensions.sh /build_files/shared/build-gnome-extensions.sh +COPY /build_files/shared/checkpoint-rpmdb.sh /build_files/shared/checkpoint-rpmdb.sh COPY /build_files/shared/clean-stage.sh /build_files/shared/clean-stage.sh COPY /build_files/shared/disable-repos.sh /build_files/shared/disable-repos.sh COPY /build_files/shared/finalize-gnome-extensions.sh /build_files/shared/finalize-gnome-extensions.sh @@ -86,7 +87,8 @@ RUN --mount=type=cache,dst=/var/cache/libdnf5 \ export PATH="/tmp/scripts/helpers:$PATH" && \ /ctx/build_files/base/03-packages.sh && \ /ctx/build_files/base/04-install-kernel-akmods.sh && \ - /ctx/build_files/base/05-override-install.sh \ + /ctx/build_files/base/05-override-install.sh && \ + /ctx/build_files/shared/checkpoint-rpmdb.sh \ ' # hadolint ignore=DL3006 @@ -150,7 +152,8 @@ RUN --mount=type=cache,dst=/var/cache/libdnf5 \ /ctx/build_files/base/19-initramfs.sh && \ /ctx/build_files/shared/validate-repos.sh && \ /ctx/build_files/shared/clean-stage.sh && \ - /ctx/build_files/base/20-tests.sh \ + /ctx/build_files/base/20-tests.sh && \ + /ctx/build_files/shared/checkpoint-rpmdb.sh \ ' # Embed the Stable container-native ISO contract after Stage 2. This runs diff --git a/build_files/base/20-tests.sh b/build_files/base/20-tests.sh index bd56824d3..a36960379 100755 --- a/build_files/base/20-tests.sh +++ b/build_files/base/20-tests.sh @@ -35,6 +35,13 @@ test -f /usr/share/ublue-os/homebrew/fonts.Brewfile test -f /usr/share/ublue-os/homebrew/preinstall.d/bluefinctl.Brewfile test -f /usr/share/ublue-os/homebrew/preinstall.d/system-cli.Brewfile test -x /usr/bin/brew-preinstall +# ExecStart names /usr/bin/brew-preinstall, but that is a two-line trampoline +# (`exec /usr/libexec/brew-preinstall`). Asserting only the trampoline still +# passes when the payload it exec's is renamed or dropped upstream -- the unit +# then fails at first login with status 127 and bluefinctl silently never +# installs, which is exactly the symptom reported in #965. Assert the file that +# actually does the work, not just the one systemd calls. +test -x /usr/libexec/brew-preinstall test -f /usr/lib/systemd/user/brew-preinstall.service grep -q '^enable brew-preinstall\.service$' /usr/lib/systemd/user-preset/01-brew-preinstall.preset diff --git a/build_files/shared/checkpoint-rpmdb.sh b/build_files/shared/checkpoint-rpmdb.sh new file mode 100755 index 000000000..7b606290b --- /dev/null +++ b/build_files/shared/checkpoint-rpmdb.sh @@ -0,0 +1,45 @@ +#!/usr/bin/bash + +echo "::group:: ===$(basename "$0")===" + +set -eoux pipefail + +# Leave the rpmdb as a single self-contained file in the committed layer. +# +# The Fedora bootc base images ship /usr/lib/sysimage/rpm/rpmdb.sqlite in +# SQLite WAL journal mode together with stale rpmdb.sqlite-{shm,wal} +# sidecars, and every dnf transaction in a build stage switches the database +# back to WAL and leaves fresh sidecars behind. A layer committed in that +# state is not self-contained: the next stage's first rpmdb read must +# reconstruct WAL state through overlayfs, which is where CI's +# "database disk image is malformed" failures come from (issue #995; +# docs/skills/ci/references/failure-modes.md). Checkpointing the WAL and +# switching to the default rollback-journal mode makes the database an +# ordinary single file that any later stage — and the shipped image — can +# read without WAL machinery. Run this as the last step of any RUN whose +# rpmdb a later stage or the final image will read. + +# RPMDB_PATH: absolute path of the rpmdb SQLite database. +# Defaults to the bootc sysimage location; overridden in unit tests. +RPMDB_PATH="${RPMDB_PATH:-/usr/lib/sysimage/rpm/rpmdb.sqlite}" + +# sqlite3.connect() would silently create an empty database at a wrong path; +# a build without an rpmdb here is broken and must fail now, not later. +if [[ ! -f "${RPMDB_PATH}" ]]; then + echo "checkpoint-rpmdb: no rpmdb at ${RPMDB_PATH}" >&2 + exit 1 +fi + +python3 - "${RPMDB_PATH}" <<'PYEOF' +import sqlite3 +import sys + +db = sqlite3.connect(sys.argv[1]) +db.execute("PRAGMA wal_checkpoint(TRUNCATE)") +db.execute("PRAGMA journal_mode=DELETE") +db.close() +PYEOF + +rm -f "${RPMDB_PATH}-shm" "${RPMDB_PATH}-wal" + +echo "::endgroup::" diff --git a/docs/skills/ci/SKILL.md b/docs/skills/ci/SKILL.md index 55d150d0c..ddf05f7ae 100644 --- a/docs/skills/ci/SKILL.md +++ b/docs/skills/ci/SKILL.md @@ -47,22 +47,24 @@ gh run view RUN_ID --repo projectbluefin/bluefin --log-failed gh run rerun RUN_ID --repo projectbluefin/bluefin --failed-only ``` -Read the actual workflow before describing or changing its behavior. Shared -logic belongs in the reusable workflow that owns it; callers should stay thin. +Read the actual workflow before changing its behavior. Shared logic belongs in +the reusable workflow that owns it; callers should stay thin. The `unit-tests` job in `pr-validation.yml` runs BATS with kcov and publishes `bats-tap-results` plus `bats-kcov-report` artifacts for shell-test visibility. Coverage runs route child `bash