Skip to content

ci: promote testing images to stable (96dec33c80b7b4e3) - #797

Merged
castrojo merged 3 commits into
mainfrom
auto/promote-testing-to-main
Jun 11, 2026
Merged

ci: promote testing images to stable (96dec33c80b7b4e3)#797
castrojo merged 3 commits into
mainfrom
auto/promote-testing-to-main

Conversation

@castrojo

@castrojo castrojo commented Jun 11, 2026

Copy link
Copy Markdown
Contributor

:testing → :stable

Days since last stable 12d (2026-05-30)
Commits since last stable 25
Updated 2026-06-11T18:30:00Z

Images

Variant Digest
dakota sha256:96dec33c80b7b4e3f7da2d2b9ba87a8a231444f0c43b1398fb5adf6122652684
dakota-nvidia sha256:8439ab66fb4635ce815695556529908f6b993e9ff39150a133d7b531c51b9a70

Components

Component Track Ref
gnome-build-meta gnome-50 50.2-0-g55dd50.2-0-g55dd
freedesktop-sdk freedesktop-sdk-25.08* freedesktop-freedesktop-

Merge this PR (2 × maintainers required). When merged, execute-release.yml re-verifies cosign signatures and copies each digest to :stable.

@castrojo
castrojo requested a review from a team as a code owner June 11, 2026 18:02
@coderabbitai

coderabbitai Bot commented Jun 11, 2026

Copy link
Copy Markdown

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 1d62f519-3ae7-4975-8fbf-bf6d8ede2188

📥 Commits

Reviewing files that changed from the base of the PR and between 50882c8 and 26ef300.

📒 Files selected for processing (1)
  • .github/release-state.yaml

📝 Walkthrough

Walkthrough

This PR updates .github/release-state.yaml to populate new SHA256 digests for dakota and dakota-nvidia container images in the testing section and advances the updated_at timestamp to 2026-06-11T18:00:00Z.

Changes

Release State Configuration

Layer / File(s) Summary
Update testing digests and timestamp
.github/release-state.yaml
Testing digests for dakota and dakota-nvidia are refreshed with new SHA256 values, and the state file timestamp is updated.

Estimated code review effort

🎯 1 (Trivial) | ⏱️ ~2 minutes

Possibly related PRs

  • projectbluefin/dakota#757: The new testing digests in this PR directly align with PR #757's automation that writes testing digests to this file on the auto/promote-testing-to-main branch.
  • projectbluefin/dakota#636: The testing digest updates correspond to the testing stream layout and digest-pinning behavior introduced by the CI stream layout PR.

Suggested labels

area/ci, pr/needs-review

Suggested reviewers

  • p5
  • tulilirockz

Poem

🐰 Fresh digests in testing wait,
Dakota's ready at the gate,
Timestamps march, sha256 gleams,
Release state flows in smooth streams,
Hopping forward with each beam! 🎯

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately reflects the main change: promoting testing container images to stable via the commit hash reference.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch auto/promote-testing-to-main

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@github-actions

github-actions Bot commented Jun 11, 2026

Copy link
Copy Markdown
Contributor

Release status

Status: ready
Last checked (UTC): 2026-06-11T18:12:53.037Z

Variants

Variant Source tag Digest
dakota testing sha256:96dec33c80b7b4e3
dakota-nvidia testing sha256:8439ab66fb4635ce

Checks

Check Result Details
Resolve digests passed Resolved 2 digest(s) from testing.
Verify cosign signatures passed All resolved digests passed cosign verification.
Check post-testing-e2e (skipped) skipped E2E check disabled by caller. Caller disabled the E2E gate.

Per-variant signature results

Variant Signature
dakota passed
dakota-nvidia passed

@github-actions

Copy link
Copy Markdown
Contributor

Thanks for the PR! A maintainer will review it.

While you wait, make sure these pass locally:

just validate        # element graph check
just build default   # build the image
just boot-test       # confirm the desktop boots (exits 0 = pass)
just lint            # bootc container lint

If this PR fixes a bug, add verify steps to the linked issue so users can confirm the fix on their hardware after the next nightly ships:

```verify
ujust <something>   # what users should run to confirm the fix
```

@coderabbitai coderabbitai Bot added the area/ci label Jun 11, 2026
@castrojo castrojo changed the title ci: promote testing images to stable (96dec33c80b7) ci: promote testing images to stable (96dec33c80b7b4e3) Jun 11, 2026
@castrojo
castrojo merged commit 9e5d852 into main Jun 11, 2026
12 checks passed
@castrojo
castrojo deleted the auto/promote-testing-to-main branch June 11, 2026 18:12
castrojo added a commit that referenced this pull request Jun 12, 2026
* ci: restore testing→main promotion pipeline — parity with bluefin

Four changes to bring Dakota's release pipeline into parity with bluefin:

1. sync-main-to-testing.yml (new)
   After each squash-merge promotion, the squash commit lands on main but
   not in testing, leaving testing permanently behind. Without sync,
   the next promote run hits an empty squash → git commit exits 1.
   This workflow merges main back into testing on every push to main,
   keeping the cycle alive. Matches bluefin/bluefin-lts exactly.

2. renovate.json5: baseBranchPatterns main → testing
   Renovate must target testing (not main) so new dependency updates
   stage on testing first. The promote workflow then squash-merges them
   to main, which triggers execute-release to retag :testing → :stable.
   With Renovate targeting main directly, testing is a dead branch and
   the promotion PR never opens. Parity with bluefin.

3. pr-triage.yml: exempt Renovate PRs targeting testing from main gate
   The triage workflow blocks all PRs not targeting main. Renovate PRs
   to testing must be explicitly exempted or they are immediately blocked
   and cannot automerge, breaking the staging pipeline.

4. promote-testing-to-main.yml: bump actions SHA 6c2278a → efa19f2c
   Picks up 9 commits of fixes: PR body rendering, team review requests,
   e2e gate SHA correctness, source/target branch inputs. Companion fix
   for the empty-squash crash is in projectbluefin/actions PR#218.

Root cause of the original breakage: PR #741 (point Renovate at main) was
landed without adding sync-main-to-testing. After the first promotion (#797)
the cycle broke — testing fell behind, the nightly promote kept crashing
(startup_failure from missing perms, then exit-1 from empty squash).

Closes #NNN

Assisted-by: Claude Sonnet 4.5 via pi

* docs(skills): ci — testing→main promotion cycle, invariants, and failure modes

Document the three invariants required for the promotion pipeline to work,
the empty-squash crash root cause and fix, and the 2026-06-11/12 breakage
chain. Future agents: read this before touching Renovate config, branch
targets, or the pr-triage gate.

Assisted-by: Claude Sonnet 4.5 via pi
castrojo added a commit that referenced this pull request Jun 21, 2026
…nges (#986)

* docs: add image streams section to README (#736)

Documents the :latest/:testing/:next/:btw image streams with a table
and bootc switch commands for the rolling :next/:btw stream.

Assisted-by: Claude Sonnet 4.6 via GitHub Copilot

* revert(uutils-coreutils): remove ghost-specific LTO override (#737)

* revert(uutils-coreutils): remove ghost-specific LTO override

The CARGO_PROFILE_RELEASE_LTO=thin env var was added to fix a SIGABRT
on ghost lab, but setting it in the element changes the BST cache key
and forces a full cold rebuild in CI (626+ elements, ~5.5h), blowing
the 330-minute GitHub Actions limit.

Ghost-local workarounds belong in ~/.config/buildstream/userconfig.yaml
on the ghost machine, not in elements.

Assisted-by: Claude Sonnet 4.6 via GitHub Copilot

* docs(skills): add build timeout diagnosis and expand ghost userconfig lesson

- Ghost/element lesson: add real example (uutils-coreutils LTO, 626-element
  cold rebuild, 5.5h timeout) so future agents recognise the pattern immediately
- New lesson: diagnosing 330-min build timeouts — log download commands,
  finding stuck 'Waiting for remote build' elements, root cause table

Assisted-by: Claude Sonnet 4.6 via GitHub Copilot

* ci(e2e): remove pull_request trigger from e2e workflow (#740)

PRs cannot publish a testing build, so the smoke suite was running
against the stale ghcr.io/projectbluefin/dakota:testing image — not
the code under review. This wastes CI resources and gives no signal
about the PR itself.

e2e is now workflow_dispatch only. The should-run gate job (which only
existed to skip e2e on PRs with no relevant file changes) is also
removed since it is no longer needed.

Assisted-by: Claude Sonnet 4.6 via GitHub Copilot

* fix(sbom): drop --privileged from bst show / buildstream-sbom runs (#745)

bst show and buildstream-sbom are read-only operations on BST YAML
element files — no FUSE mounts or elevated capabilities needed.

--privileged caused crun (resolute/Ubuntu 26.04) to probe
/run/user/1001/crun/.cache/systemd-missing-properties, which
fails with Permission denied in the GHA runner environment and
breaks the Generate SBOM step in the publish workflow.

Rootless, unprivileged podman is sufficient for these calls.

Assisted-by: Claude Sonnet 4.6 via GitHub Copilot

* fix(sbom): add seccomp=unconfined to just sbom podman invocations (#747)

crun 1.21 (resolute/Ubuntu 26.04) caches compiled seccomp BPF programs
via linkat(). This fails with EPERM on the GitHub Actions runner because
the kernel's protected_hardlinks sysctl or user-namespace restrictions
prevent the linkat from the seccomp cache to the container bundle path.

The previous fix (#745) dropped --privileged but did not address the
root cause: crun fails on any podman container start when it tries to
cache the seccomp BPF filter, regardless of privilege level.

Adding --security-opt seccomp=unconfined prevents crun from compiling
or caching any seccomp BPF profile, avoiding the linkat entirely.
bst show and buildstream-sbom are read-only BST operations with no
need for seccomp filtering.

Fixes: https://github.com/projectbluefin/dakota/actions/runs/27144460494/job/80118251435

Assisted-by: Claude Sonnet 4.6 via GitHub Copilot

* fix(sbom): use runc runtime to bypass crun 1.21 GHA runner failures (#749)

* fix(sbom): use runc runtime to bypass crun 1.21 GHA runner failures

crun 1.21 (resolute/Ubuntu 26.04) has two failure modes on GHA runners:

1. linkat .cache/seccomp/... EPERM — crun caches compiled seccomp BPF
   programs via linkat(); fails because the runner's kernel blocks
   hard-links from the seccomp cache to the container bundle path.

2. opendir /run/user/1001/crun/.cache/systemd-missing-properties EACCES —
   crun probes systemd presence and caches the result; fails because
   /run/user/1001/crun/ is owned by root from a previous privileged run
   or was never initialised in the non-login GHA environment.

Both issues are crun-specific behaviours absent in runc.
bst show and buildstream-sbom are read-only BST operations with no need
for crun's advanced features. Switching to runc (present on ubuntu-24.04
runners via Docker) bypasses both failure modes without any privilege or
seccomp workarounds.

Fixes: https://github.com/projectbluefin/dakota/actions/runs/27146970327/job/80127338444

Assisted-by: Claude Sonnet 4.6 via GitHub Copilot

* docs(skills): document crun 1.21 just sbom failure modes and runc fix

Assisted-by: Claude Sonnet 4.6 via GitHub Copilot

* ci(next): wire next branch into main CI pipeline (#751)

* ci(next): wire next branch into main CI pipeline

- publish.yml: add next to workflow_run branches filter so publish
  triggers when a next branch build completes (workflow_run only fires
  from default branch — gnome-51's copy was dead)
- publish.yml: branch-aware concurrency group (main vs next queues)
- publish.yml: output branch + testing_tag from setup job; promote to
  :next instead of :testing for next builds; push :btw alias
- publish.yml: fast-forward testing branch only for main builds
- build.yml: add next to pull_request + merge_group branches so PRs
  targeting next trigger the build via merge queue
- build.yml: branch-aware BST show cache key to prevent next
  (GNOME master junctions) from colliding with main (stable)
- track-next-junctions.yml: add to main (schedule triggers only run
  from the default branch — nightly junction tracking was never firing)

The 'next' branch is a permanent forward-tracking stream. When GNOME 51
ships and GNOME 52 opens on master, only elements/gnome-build-meta.bst
needs updating on the next branch — no CI changes required.

Assisted-by: Claude Sonnet 4.6 via GitHub Copilot

* fix(ci): restore testing fast-forward for merge-queue and clarify next auto-merge

- publish.yml: fix fast-forward condition to accept both 'main' and
  'gh-readonly-queue/main/*' head_branch values. The merge-queue flow
  sets head_branch to 'gh-readonly-queue/main/pr-N', never 'main',
  so the previous condition would silently skip all fast-forwards.
- track-next-junctions.yml: remove misleading 'no auto-merge, human
  review required' comment. The :next/:btw stream is fully automated
  rolling nightly with no human gate — auto-merge is intentional.

Assisted-by: Claude Sonnet 4.6 via GitHub Copilot

* fix(ci): make testing branch fast-forward idempotent

GitHub API returns 422 when PATCHing a ref to its current SHA
(not-a-fast-forward) then the fallback POST also fails with
'Reference already exists'. Handle three cases explicitly:
- already at target SHA → no-op (idempotent re-runs)
- branch missing → POST to create
- branch behind → PATCH to advance

Assisted-by: Claude Sonnet 4.6 via GitHub Copilot

* docs(skills): add ci.md lessons from PR 751 testing fast-forward and next stream (#752)

- Document GitHub API 422 idempotent fast-forward pattern
- Document merge-queue head_branch never equals 'main'
- Document :next/:btw as fully automated rolling stream (no human gate)

Assisted-by: Claude Sonnet 4.6 via GitHub Copilot

* chore(ci): remove self-hosted Renovate workflow — GitHub App handles this

The renovate.yml GHA workflow has never worked (RENOVATE_TOKEN was never
set). The projectbluefin Renovate GitHub App (id 92805263) creates all
dependency PRs. This workflow is dead weight that fails startup every 6h.

Assisted-by: Claude Sonnet 4.6 via pi

* ci: add testing branch to build.yml PR triggers

Renovate and auto/track-* PRs target the testing branch for auto-merge
before promotion to main. The build workflow must run on testing-targeted
PRs so the renovate-automerge workflow can fire when validation passes.

Assisted-by: Claude Sonnet 4.6 via pi

* ci: add Renovate auto-merge workflow

Fires when Build Bluefin dakota completes successfully on a testing-
targeted PR from renovate[bot] or app/mergeraptor. Enables native GitHub
auto-merge so PRs land without manual intervention.

Mirrors the pattern used in projectbluefin/bluefin and bluefin-lts.

Assisted-by: Claude Sonnet 4.6 via pi

* ci: target testing branch for auto-merge group PRs

auto-merge group (brew, common, extensions, etc.) should land on testing
first — no review gate — and flow to main via weekly promotion. This
unblocks auto-merge which was stuck because main requires 1 review.

manual-merge and core-junctions still target main (need human review).

Assisted-by: Claude Sonnet 4.6 via pi

* revert: restore track-bst-sources.yml (previous commit corrupted it)

* ci: target testing branch for auto-merge group PRs

auto-merge group (brew, common, extensions, etc.) should land on testing
first — no review gate — and flow to main via weekly promotion. This
unblocks auto-merge which was stuck because main requires 1 review.

manual-merge and core-junctions still target main (require human review).

Assisted-by: Claude Sonnet 4.6 via pi

* fix(ci): skip storage-service in export/publish jobs (#756)

The remote CAS storage-service routes the local casd through
cache.projectbluefin.io. GNOME 51 produces ~8.5GB artifacts which
exceed the remote server's per-client quota during bst export, causing:
  OutOfSpaceException: Insufficient storage quota

Build jobs (push=true) keep storage-service — artifacts are written
directly to the remote CAS as they are built.

Export/publish jobs (push=false) now use local disk for the casd.
The runner's BTRFS volume has sufficient space to materialise the
artifact for podman push.

Assisted-by: Claude Sonnet 4.6 via GitHub Copilot

* ci: build on push to testing branch

Assisted-by: Claude Sonnet 4.6 via pi

* docs: document :next/:btw stream, export CAS fix, and next branch ops (#758)

AGENTS.md:
- Add :next/:btw to repo map and image stream summary
- Clarify no stable promotion ever for next stream

docs/skills/overview.md:
- Add Image Streams table (testing/latest/stable/next/btw)
- Add :next/:btw section — positioning, key differences, ops patterns

docs/skills/ci.md:
- export/publish jobs skip storage-service (remote CAS quota too small
  for GNOME 51 ~8.5GB artifacts)
- Cold build strategy for next branch: retrigger until cache warms
- next branch needs manual cherry-picks of main fixes
- :next build only fires on junction bumps, not guaranteed nightly

docs/skills/README.md:
- Add routing table entry for :next/:btw stream

Assisted-by: Claude Sonnet 4.6 via GitHub Copilot

* feat(ci): add PR-based release gate; replace weekly-testing-promotion (#757)

Add the PR-as-gate release model to dakota. The weekly cron-based promotion
workflow is replaced by an always-open auto/promote-testing-to-main PR
that maintainers merge (requires 2 approvals) to cut a stable release.

Dakota does not have a testing->main code flow (images publish directly
from main). A .github/release-state.yaml tracking file is used to create
a real diff on the promotion branch, recording the :testing digests
being promoted.

New workflows:
- promote-testing-to-main.yml: triggers on push to testing branch.
  Resolves :testing digests for dakota and dakota-nvidia. Creates/updates
  auto/promote-testing-to-main branch with release-state.yaml. Opens or
  updates the promotion PR against main.
- pr-release-gate.yml: gate checks on the promotion PR (digest resolution,
  cosign verification). Sticky status comment. release/ready or
  release/blocked label. E2E gate disabled (no post-testing-e2e workflow
  in this repo).
- release-reminder.yml: daily cron. Reminder after 7 days; escalates at 14.
- execute-release.yml: runs on PR merge; promotes testing -> stable for
  dakota and dakota-nvidia. Creates GitHub release.

New file:
- .github/release-state.yaml: initial stub; updated by promote-testing-to-main.yml.

Deleted:
- weekly-testing-promotion.yml: replaced by the above.

Variants promoted: dakota, dakota-nvidia (testing -> stable)

Depends on: projectbluefin/actions#135 (must merge first)

Assisted-by: Claude Sonnet 4.6 via GitHub Copilot

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* fix(ci): dispatch pr-release-gate when promote branch is updated

GITHUB_TOKEN pushes from promote-testing-to-main.yml do not trigger
pull_request synchronize events (GitHub anti-loop protection). Add an
explicit workflow_dispatch trigger to pr-release-gate.yml and dispatch it
from promote-testing-to-main.yml after each PR update so gate checks always
run against the current promotion state.

Assisted-by: Claude Sonnet 4.6 via GitHub Copilot
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* fix(ci): inline gate as job in promote workflow instead of dispatch

GITHUB_TOKEN cannot trigger workflow_dispatch events (anti-loop protection).
Replace the failed dispatch approach with an inline gate job that calls
reusable-release-gate.yml@main directly after the promote job completes.

Assisted-by: Claude Sonnet 4.6 via GitHub Copilot
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* fix(ci): remove dead release.yml, add daily promote schedule, fix project_name casing

Three cleanups from org-wide CI audit:

1. Remove release.yml (dead code): The old workflow_call-triggered release
   workflow has no callers since execute-release.yml now handles releases
   via the PR-merge trigger with reusable-release.yml@main. Leaving both
   creates confusion about which release path is canonical.

2. Add daily schedule to promote-testing-to-main: bluefin and bluefin-lts
   both run at cron '0 23 * * *' in addition to push/dispatch triggers.
   Without the schedule, the promotion PR can go stale if no build pushes
   to testing for a day.

3. Fix project_name: 'Bluefin dakota' -> 'Dakota' to match repo branding
   conventions (bluefin uses 'Bluefin', lts uses 'Bluefin LTS').

Assisted-by: Claude Sonnet 4.6 via GitHub Copilot

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* docs(skills): add live build diagnosis and gnome-build-meta nightly delta patterns (#742)

Two new lessons from a session investigating a slow in-progress build:

1. Diagnosing slow in-progress builds via GitHub API — gh api job logs,
   counting SKIPPED vs compiling, live log truncation caveat (~23K line
   snapshot cap), when to re-trigger vs let it run.

2. gnome-build-meta nightly delta pattern — after the GNOME nightly (~08:00
   UTC), 10–30 core-deps elements rebuild; this is expected, takes 60–120
   extra minutes, and completes within the 330-min timeout. Lists commonly
   rebuilt elements and how to confirm nightly drift vs a local cache bust.

Assisted-by: Claude Sonnet 4.6 via GitHub Copilot

* fix(ci): point Renovate at main, exempt bonedigger from pinning (#741)

baseBranchPatterns was set to ["testing"] but the testing branch is
a stale diverged pointer — the CI gate (on-pr-opened-or-updated) blocks
any PR not targeting main, so Renovate PRs targeting testing can never
pass or merge.

Two fixes:
1. baseBranchPatterns: ["testing"] → ["main"] so new Renovate PRs
   target main and pass the CI gate
2. Add packageRules entry to exempt projectbluefin/bonedigger from
   digest pinning — @main is an intentional managed floating tag
   (documented in bonedigger.yml and docs/skills/ci-tooling.md)

Assisted-by: Claude Sonnet 4.6 via GitHub Copilot

* chore(build): upgrade chunkah v0.5.0 to v0.6.0

Bump CHUNKAH_REF to v0.6.0 (sha256:ff8b8b...). chunkah#113 closed via overlay workaround; overlay+fakecap-restore path unchanged.

Assisted-by: Claude Sonnet 4.6 via GitHub Copilot
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* chore(deps): update bootc: v1.15.2-0 -> v1.16.0-0 (#765)

Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>

* fix(ci): publish :testing on every testing merge; fix track-bst branch base (#766)

* fix(ci): track-bst-sources: branch from target base, not main

auto-merge PRs target the testing branch, but the workflow was creating
the tracking branch from origin/main. This caused two problems:

1. All commits that main had but testing lacked showed up in the PR,
   inflating the commit count and causing CI confusion.
2. If main and testing had diverged on workflow files, the PR diff
   included CI regressions (e.g. PR 764 would have removed testing
   branch triggers from build.yml).

Fix: determine BASE_BRANCH before the git checkout, stash the
BST-tracked element changes, switch to origin/$BASE_BRANCH, then
pop the stash. The PR diff is now relative to the actual target
branch, and only contains the element ref update.

Also commit docs/skills/vm-stack.md: VM stack (virt-manager + QEMU
flatpaks) lessons from 2026-06-09 session.

Assisted-by: Claude Sonnet 4.6 via pi

* fix(ci): publish :testing on every merge to testing branch

publish.yml was only triggered by builds on main and next. Auto-merge
PRs target the testing branch, so their builds completed successfully
but no image was ever published. promote-testing-to-main.yml fires on
push to testing and immediately does skopeo inspect dakota:testing —
this silently failed whenever testing advanced without a prior main
publish.

Fix: add testing and gh-readonly-queue/testing/** to the workflow_run
branches filter, allow testing in the setup job condition, and map the
testing branch to testing_tag=testing. The fast-forward-testing step
in the promote job is already gated on branch==main so there is no
loop.

Result: every merge to testing now publishes :testing immediately,
matching the bluefin/bluefin-lts behaviour.

Assisted-by: Claude Sonnet 4.6 via pi

* fix(ci): track-bst-sources: use --squash for auto-merge, not --merge (#767)

The repo has allowMergeCommit=false — only squash merges are permitted.
The previous --merge call silently fell through to the warning echo,
leaving auto-merge never set on tracking PRs. Switch to --squash.

Assisted-by: Claude Sonnet 4.6 via pi

* feat(ci): use centralized bootc-build/chunka action; remove dead code (#773)

* feat(ci): use centralized bootc-build/chunka action for chunkify

Replace the inline `just chunkify` step in publish.yml with the
centralized `projectbluefin/actions/bootc-build/chunka` composite
action. Dakota uses the BST code path (xattr-manifest input) which:

  - Mounts a writable overlay on the exported OCI rootfs
  - Injects user.component xattrs from files/fakecap-manifest.tsv
  - Runs chunkah via podman run against the overlay
  - Returns the rechunked image in user podman storage

The fakecap-manifest.tsv is pre-committed (Cargo.lock pattern) and
regenerated by .github/workflows/update-filemap.yml when BST elements
change. max-layers kept at 120 (matching previous just chunkify value).

Also removes scripts/apply-xattrs.py (dead code — superseded by
fakecap-restore.c for local dev and inject-xattrs.py in the action for
CI) and fixes a stale comment in fakecap-restore.c that claimed
coreos/chunkah#113 would eventually deliver a libc fallback (it
closed without that; the overlay approach is the resolution).

Depends on: projectbluefin/actions#<PR> (xattr-manifest input)
SHA must be updated to post-merge actions SHA before this lands.

Closes #769
Closes #770
Closes #771
Closes #772

Assisted-by: Claude Sonnet 4.5 via pi

* fix(ci): pin chunka action to post-merge SHA cb230ad

Update the SHA pin from the feature branch commit to the squash-merged
SHA on projectbluefin/actions main after actions#141 landed.

Assisted-by: Claude Sonnet 4.5 via pi

* docs: expand CONTRIBUTING.md with BST build context and AGENTS.md reference

Contributors arriving from mainline Bluefin had no indication that dakota uses
BuildStream 2 (not Containerfiles/DNF) and no path to the actual build instructions.
This adds the critical context and points to AGENTS.md where everything lives.

Addresses hive advisory finding in projectbluefin/common#557

Assisted-by: Claude Sonnet 4.5 via pi

* fix(ci): fire build on main push; auto-merge PRs target main; force-sync testing mirror (#777)

Every merge to main now triggers a build and publishes :testing immediately.
No intermediate staging branch — the weekly promotion is the only gate.

Changes:
- build.yml: push trigger adds 'main' (removes 'testing' — no longer the source branch)
- publish.yml: fast-forward testing branch uses force=true so it never silently fails
- track-bst-sources.yml: auto-merge group PRs target main instead of testing,
  eliminating the branch divergence that stranded bootc 1.16 and other updates

Assisted-by: Claude Sonnet 4.5 via pi

* docs(skills): ci + pr-review — lessons from 2026-06-10 session (#774)

ci.md:
- publish.yml must include testing branch in workflow_run.branches
- track-bst-sources: branch from origin/$BASE_BRANCH not origin/main
- track-bst-sources: use --squash not --merge (allowMergeCommit=false)
- corrupted auto-track PR anatomy and how to spot one

pr-review.md:
- check new service/file additions are wired into BST install-commands

Fixes landed: PR 766 (publish testing), PR 767 (--squash automerge)

Assisted-by: Claude Sonnet 4.6 via pi

* fix(ci): gh pr create has no --json flag — capture URL instead (#778)

`gh pr create` does not support `--json`/`--jq`. Those flags only
exist on read subcommands (list, view). The workflow was using:

  PR_NUMBER=$(gh pr create ... --json number --jq '.number')

which exits with "unknown flag: --json" and code 1, preventing the
PR from being created and auto-merge from being enabled.

Fix: capture the URL that `gh pr create` outputs to stdout (same
pattern used in track-bst-sources.yml's track-core-junctions job),
then use the URL/number for `gh pr merge --auto --squash`.

Closes #NNN

Assisted-by: Claude Sonnet 4.5 via pi

* chore(ci): bump projectbluefin/actions SHA pins to 3025b5d31f34 (#781)

* chore(ci): bump projectbluefin/actions SHA pins to 3025b5d31f34

Fixes dakota publish failure: inject-xattrs.py called os.lsetxattr()
which was removed in newer Python. Fixed at new SHA.

Updates all projectbluefin/actions references across CI workflows.

Assisted-by: Claude Sonnet 4.5 via pi

* chore(ci): bump projectbluefin/actions SHA pins to 3025b5d31f34

Fixes dakota publish failure: inject-xattrs.py called os.lsetxattr()
which was removed in newer Python. Fixed at new SHA.

Updates all projectbluefin/actions references across CI workflows.

Assisted-by: Claude Sonnet 4.5 via pi

* chore(ci): bump projectbluefin/actions SHA pins to 3025b5d31f34

Fixes dakota publish failure: inject-xattrs.py called os.lsetxattr()
which was removed in newer Python. Fixed at new SHA.

Updates all projectbluefin/actions references across CI workflows.

Assisted-by: Claude Sonnet 4.5 via pi

* chore(ci): bump projectbluefin/actions SHA pins to 3025b5d31f34

Fixes dakota publish failure: inject-xattrs.py called os.lsetxattr()
which was removed in newer Python. Fixed at new SHA.

Updates all projectbluefin/actions references across CI workflows.

Assisted-by: Claude Sonnet 4.5 via pi

* chore(ci): bump projectbluefin/actions SHA pins to 3025b5d31f34

Fixes dakota publish failure: inject-xattrs.py called os.lsetxattr()
which was removed in newer Python. Fixed at new SHA.

Updates all projectbluefin/actions references across CI workflows.

Assisted-by: Claude Sonnet 4.5 via pi

* chore(ci): bump projectbluefin/actions SHA pins to 3025b5d31f34

Fixes dakota publish failure: inject-xattrs.py called os.lsetxattr()
which was removed in newer Python. Fixed at new SHA.

Updates all projectbluefin/actions references across CI workflows.

Assisted-by: Claude Sonnet 4.5 via pi

* chore(ci): bump projectbluefin/actions SHA pins to 3025b5d31f34

Fixes dakota publish failure: inject-xattrs.py called os.lsetxattr()
which does not exist in the runner Python. Fixed at new actions SHA.

Assisted-by: Claude Sonnet 4.5 via pi

* chore(ci): bump projectbluefin/actions SHA pins to 3025b5d31f34

Fixes dakota publish failure: inject-xattrs.py called os.lsetxattr()
which does not exist in the runner Python. Fixed at new actions SHA.

Assisted-by: Claude Sonnet 4.5 via pi

* chore(ci): bump projectbluefin/actions SHA pins to 3025b5d31f34

Fixes dakota publish failure: inject-xattrs.py called os.lsetxattr()
which does not exist in the runner Python. Fixed at new actions SHA.

Assisted-by: Claude Sonnet 4.5 via pi

* chore(ci): bump projectbluefin/actions SHA pins to 3025b5d31f34

Fixes dakota publish failure: inject-xattrs.py called os.lsetxattr()
which does not exist in the runner Python. Fixed at new actions SHA.

Assisted-by: Claude Sonnet 4.5 via pi

* chore(ci): bump projectbluefin/actions SHA pins to 3025b5d31f34

Fixes dakota publish failure: inject-xattrs.py called os.lsetxattr()
which does not exist in the runner Python. Fixed at new actions SHA.

Assisted-by: Claude Sonnet 4.5 via pi

* chore(ci): bump projectbluefin/actions SHA pins to 3025b5d31f34

Fixes dakota publish failure: inject-xattrs.py called os.lsetxattr()
which does not exist in the runner Python. Fixed at new actions SHA.

Assisted-by: Claude Sonnet 4.5 via pi

* fix(ci): pin reusable-workflow @main refs to SHA

Pins projectbluefin/actions @main refs to 7f79969 # v1 in execute-release.yml, pr-release-gate.yml, promote-testing-to-main.yml, release-reminder.yml. Eliminates non-deterministic CI from floating @main refs. Renovate keeps SHAs current via C3 grouping rule.

* ci: add scheduled cache-warm workflow for BST remote cache

Adds Mon/Thu 06:00 UTC scheduled build to keep BST remote cache hot, preventing cold-start timeouts. Builds oci/bluefin.bst (non-fatal), 420min timeout, checks out testing branch.

* ci(C1): thin caller for reusable-promote workflow

Replaces 183-line promote-testing-to-main.yml with a 30-line thin caller to projectbluefin/actions/reusable-promote.yml@8487dff # v1. Dakota is the canary — bluefin-lts and bluefin follow after one observed promotion cycle.

* fix(renovate): align config with org standard — extend shared config, automerge digests (#790)

Dakota's renovate config was extending config:recommended instead of
the org's local>projectbluefin/renovate-config. It also didn't automerge
'digest' type updates (SHA pins are digest type), causing drift.

Changes:
- Extends local>projectbluefin/renovate-config (gets org rules)
- Adds digest to automerge matchUpdateTypes
- Adds projectbluefin/actions grouping rule (atomic SHA PRs)
- Preserves enabledManagers: github-actions (BST sources tracked separately)
- Preserves bonedigger exclusion

Resolves: projectbluefin/common#605

Assisted-by: Claude Sonnet 4 via pi

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* fix(ci): bump projectbluefin/actions SHA to 2a09e72e9be1 (#166) (#792)

actions@3025b5d31f34 excluded dakota from sign-and-publish
certificate-identity-regexp, causing 65% publish failure rate.
actions#166 fixes the regexp and makes scan-image non-blocking.

Assisted-by: Claude Sonnet 4.5 via pi

* fix(ci): fix lifecycle wiring, remove duplicate GHCR cleanup (#789)

- bonedigger.yml: was calling projectbluefin/common/.github/workflows/
  lifecycle.yml@025ec29 (stale path, predates lifecycle migration to
  projectbluefin/actions). Updated to call factory-standard
  projectbluefin/bonedigger/.github/workflows/lifecycle.yml@30a240e
- Delete lifecycle-caller.yml: duplicate lifecycle handler firing on
  identical events as bonedigger.yml, also pointing at stale common path
- Delete clean.yml: pinned to feature branch SHA
  (feat/ghcr-cleanup-orphaned-images, fb75840b) not a release;
  duplicates ghcr-cleanup.yml which is properly release-pinned and
  runs the following day

Assisted-by: Claude Sonnet 4.5 via pi

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* feat(ci): add cliff.toml for structured changelog generation (#793)

Adds git-cliff configuration to generate structured changelogs from
Conventional Commits. Required by reusable-release.yml which calls
generate-release-notes wrapping git-cliff.

Note: omits the "chore: promote testing" skip rule — dakota uses OCI
digest promotions via "Publish Bluefin dakota", not squash promotion
commits.

Closes projectbluefin/common#609

Assisted-by: Claude Sonnet 4.5 via pi

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* feat(ci): add weekly standalone vulnerability scan (#794)

Adds a Grype-based vulnerability scan workflow that runs weekly
(Monday 08:00 UTC). Catches newly-disclosed CVEs against
already-published images between builds.

Dakota uses BuildStream (not reusable-build.yml) so there are no
image-digest artifacts — image refs are resolved directly via
skopeo against the published :latest tag.

Closes projectbluefin/common#607

Assisted-by: Claude Sonnet 4.5 via pi

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* chore(ci): remove per-repo GHCR cleanup (centralized in actions) (#795)

Org-wide GHCR cleanup is now handled in projectbluefin/actions:
projectbluefin/actions/.github/workflows/ghcr-cleanup.yml

Covers: bluefin, bluefin-nvidia, bluefin-pr, dakota, dakota-nvidia, common

Closes projectbluefin/common#603

Assisted-by: Claude Sonnet 4.5 via pi

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* fix: use @projectbluefin/maintainers team in CODEOWNERS (#796)

* chore(ci): remove per-repo GHCR cleanup (centralized in actions)

Org-wide GHCR cleanup is now handled in projectbluefin/actions:
projectbluefin/actions/.github/workflows/ghcr-cleanup.yml

Covers: bluefin, bluefin-nvidia, bluefin-pr, dakota, dakota-nvidia, common

Closes projectbluefin/common#603

Assisted-by: Claude Sonnet 4.5 via pi
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* fix: use @projectbluefin/maintainers team in CODEOWNERS instead of individual handles

Promotion PRs and other auto-assigned reviews were pinging individual
maintainers by handle. Using the team slug means membership changes
are managed in one place and new maintainers are automatically included.

Assisted-by: Claude Sonnet 4.5 via pi

---------

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* ci: promote testing images to stable (96dec33c80b7b4e3) (#797)

* ci: update testing image digests for stable promotion

* ci: promote testing images to stable (96dec33c80b7)

---------

Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>

* fix(ci): grant required permissions to execute-release caller (#798)

permissions: {} starved the GITHUB_TOKEN before any job could start,
causing startup_failure on every run since the workflow was introduced
on 2026-06-09. Same root cause as the promote-testing-to-main fix.

Top-level permissions must be a superset of what the reusable jobs need:
  actions: read       (check workflow runs)
  contents: write     (push stable tag / create release)
  id-token: write     (cosign OIDC signing in release-notes job)
  issues: write       (release gate comments)
  packages: write     (copy :testing → :stable in GHCR)
  pull-requests: write (update promotion PR)

Assisted-by: Claude Sonnet 4.5 via pi

* fix(ci): add workflow_dispatch to execute-release; fix if guard (#799)

Allows manual re-trigger when the pull_request:closed event fires
before permissions are correct, or to recover from a failed release.

Also updates the job if-condition to allow dispatch events alongside
the normal pull_request:closed path.

Assisted-by: Claude Sonnet 4.5 via pi

* fix(ci): switch execute-release to push trigger + commit-msg gate (#800)

pull_request:closed fires for every PR merged to main, and when all
jobs have false if: conditions GitHub reports startup_failure instead
of a clean skip. Adopted the bluefin-lts pattern:

- Trigger on push:main and workflow_dispatch
- check-trigger job reads the squash-merge commit message
- execute only runs when message starts with
  'ci: promote testing images to stable'
- workflow_dispatch always runs execute (manual re-trigger path)
- Proper top-level permissions (superset of all job needs)

Fixes the startup_failure that has affected every run since the
workflow was introduced on 2026-06-09.

Assisted-by: Claude Sonnet 4.5 via pi

* docs(skills): ci — release pipeline lessons from 2026-06-11 session (#802)

* fix(ci): switch execute-release to push trigger + commit-msg gate

pull_request:closed fires for every PR merged to main, and when all
jobs have false if: conditions GitHub reports startup_failure instead
of a clean skip. Adopted the bluefin-lts pattern:

- Trigger on push:main and workflow_dispatch
- check-trigger job reads the squash-merge commit message
- execute only runs when message starts with
  'ci: promote testing images to stable'
- workflow_dispatch always runs execute (manual re-trigger path)
- Proper top-level permissions (superset of all job needs)

Fixes the startup_failure that has affected every run since the
workflow was introduced on 2026-06-09.

Assisted-by: Claude Sonnet 4.5 via pi

* docs(skills): ci — release pipeline lessons from 2026-06-11 session

Five new patterns from the stable release unblocking session:

- permissions: {} at workflow level starves GITHUB_TOKEN for reusable
  workflow jobs (startup_failure with jobs: [])
- pull_request: closed fires for every merge; false if: conditions
  produce startup_failure — use push: main + commit-msg gate instead
- CODEOWNERS: use @org/team-slug, not individual handles
- Promotion PR noise: suppress CodeRabbit with @coderabbitai ignore
- Promotion PR body: include days-since-stable + component diff table
- GitHub Release body 125k character limit causes HTTP 422

Also adds routing table entries for reusable workflow startup_failure
debugging and the stable promotion flow.

Assisted-by: Claude Sonnet 4.5 via pi

* feat(umotd): add umotd element (#762)

Adds a BST element that downloads the umotd binary (amd64 + arm64)
and installs it to /usr/bin/umotd.

umotd.sh in /etc/profile.d/ (from projectbluefin/common shared files)
calls umotd on every terminal open. Without this element the binary
was missing, causing 'bash: umotd: command not found' twice on startup
(dakota issue 753).

The umotd config is installed via common.bst (system_files/shared/etc/umotd/
after projectbluefin/common PR 542 merges; system_files/bluefin/etc/umotd/
at the current ref — both paths are copied by common.bst install-commands
so the config is available either way).

Assisted-by: Claude Sonnet 4.6 via GitHub Copilot

* ci: use @v1 for first-party actions references

* ci: pin reusable-promote to 9e4ba16 (Design C + gate fix)

* ci: use @v1 for first-party actions reference (no SHA pin)

* chore(deps): update common: v2026.06-186 -> v2026.06-238 (#791)

Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>

* feat(ci): add cache-warm scheduled workflow (#782)

* feat(ci): add cache-warm scheduled workflow

Adds a scheduled workflow that builds the default variant on Monday and
Thursday at 06:00 UTC (and on manual dispatch) to keep the remote CAS at
cache.projectbluefin.io warm.

Context: dakota BST builds have a 360-min job timeout. After junction ref
bumps or upstream gnome-build-meta rebuilds, the full GNOME stack must
rebuild from scratch, which can exceed 6 hours. Pre-warming the cache on
a schedule means merge-queue builds land on cache hits even after upstream
churn, eliminating the cold-start non-determinism documented in
projectbluefin/common automation audit (ND1).

Failures are non-blocking — the warm build is best-effort. The workflow
runs in its own concurrency group so it never preempts a merge-queue build.

docs/skills/ci.md updated with the new trigger row and a cache-warm
section describing intent and behavior.

Tracks: projectbluefin/common#583 (automation audit Phase 7)

Assisted-by: Claude Sonnet 4.5 via pi

* fix(ci): use generate-bst-ci-config action and persist-credentials: false

- Replace manual cert-writing with the generate-bst-ci-config composite
  action (enable-remote-execution: false, enable-push: true) so the
  BuildStream config file is actually generated and cert paths match
  what the container expects (/src/client.{crt,key})
- Add persist-credentials: false to the checkout step

Addresses CodeRabbit review comments.

* chore(ci): allow common.bst updates to automerge without review (#807)

* fix(ci): switch execute-release to push trigger + commit-msg gate

pull_request:closed fires for every PR merged to main, and when all
jobs have false if: conditions GitHub reports startup_failure instead
of a clean skip. Adopted the bluefin-lts pattern:

- Trigger on push:main and workflow_dispatch
- check-trigger job reads the squash-merge commit message
- execute only runs when message starts with
  'ci: promote testing images to stable'
- workflow_dispatch always runs execute (manual re-trigger path)
- Proper top-level permissions (superset of all job needs)

Fixes the startup_failure that has affected every run since the
workflow was introduced on 2026-06-09.

Assisted-by: Claude Sonnet 4.5 via pi

* docs(skills): ci — release pipeline lessons from 2026-06-11 session

Five new patterns from the stable release unblocking session:

- permissions: {} at workflow level starves GITHUB_TOKEN for reusable
  workflow jobs (startup_failure with jobs: [])
- pull_request: closed fires for every merge; false if: conditions
  produce startup_failure — use push: main + commit-msg gate instead
- CODEOWNERS: use @org/team-slug, not individual handles
- Promotion PR noise: suppress CodeRabbit with @coderabbitai ignore
- Promotion PR body: include days-since-stable + component diff table
- GitHub Release body 125k character limit causes HTTP 422

Also adds routing table entries for reusable workflow startup_failure
debugging and the stable promotion flow.

Assisted-by: Claude Sonnet 4.5 via pi

* chore(ci): allow common.bst updates to automerge without review

elements/bluefin/common.bst is auto-updated by mergeraptor on every
common release. The elements/ CODEOWNERS rule was blocking automerge
by requiring a maintainer approval.

Add a no-owner override for common.bst so it is exempt from the
code-owner review requirement. Also added mergeraptor to the branch
protection bypass_pull_request_allowances so the required-review count
does not block it either.

* fix(ci): exempt auto/track-next-junction from main-target gate

The PR triage workflow was blocking mergeraptor's automated junction
bumps to the next branch because the 'Block PRs not targeting main'
step did not know about this legitimate automated target.

Allow base=next when head=auto/track-next-junction, and skip the
pr/needs-review label + instructions comment for that branch.

* Add DeepWiki badge to README

* chore(ci): migrate promote-testing-to-main to reusable-promote-squash (#811)

* docs(skills): ci — lessons from 2026-06-12 session (#816)

- Update Workflow Files table: replace stale weekly-testing-promotion.yml
  and release.yml with promote-testing-to-main.yml, execute-release.yml,
  and vulnerability-scan.yml
- Fix GitHub Release body limit lesson: mark actions#191 as done
- Add CODEOWNERS no-owner override pattern for auto-managed files
- Add sign-and-publish cert identity regexp lesson (PR #792 root cause)
- Add cliff.toml requirement for reusable-release.yml (PR #793)
- Update README routing table with 3 new entry points

Assisted-by: Claude Sonnet 4.5 via pi

* chore(ci): use reusable validate-renovate workflow from actions (#812)

* chore(ci): use reusable-vulnerability-scan from actions (#813)

Replaces ~3.2KB of inline Grype scan logic with a thin caller.
Scan behavior is identical — logic now lives in projectbluefin/actions.

Ref: https://github.com/projectbluefin/actions/pull/207

Assisted-by: Claude Sonnet 4.5 via pi

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* chore(deps): update common: v2026.06-238 -> v2026.06-257 (#814)

Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>

* chore(deps): update distrobox: 2.0.0 -> 1.8.2.5-150 (#815)

Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>

* fix(ci): add missing packages: read and actions: read to promote workflow (#817)

The reusable-promote-squash.yml release-gate checks need actions: read to
inspect workflow run statuses and packages: read to read image digests.
Without these, the workflow had startup_failure since PR #811 introduced
the thin caller. Matches the permissions in projectbluefin/bluefin.

Also update SHA pin to match bluefin's tested version and switch variants
to >- scalar for consistent YAML style.

Assisted-by: Claude Sonnet 4.5 via pi

* fix(ci): revert promote SHA to 6c2278 — release-gate@5f8abb not found (#819)

The bluefin SHA (5f3cab) internally calls reusable-release-gate@5f8abb
which no longer exists in the actions repo. The original 6c2278 SHA uses
@v1 tag for reusable-release-gate which is valid and works.

Keeps the permissions fix (packages: read + actions: read) from the
previous commit.

Assisted-by: Claude Sonnet 4.5 via pi

* fix(ci): use direct merge for auto-merge group — bypass not honoured by --auto (#820)

gh pr merge --auto uses GitHub's auto-merge queue which does NOT honour
bypass_pull_request_allowances. Only direct merges (without --auto) use
the bypass. Since the repo has no required status checks and mergeraptor
is in bypass_pull_request_allowances, removing --auto causes these PRs
to merge immediately when created.

This is why common.bst and distrobox.bst (and all other auto-merge group
elements) have been sitting as open PRs instead of auto-merging.

Assisted-by: Claude Sonnet 4.5 via pi

* docs(skills): ci — three patterns from 2026-06-12 session

- gh pr merge --auto does NOT honour bypass_pull_request_allowances;
  use direct merge (drop --auto) for bypass actors with no required checks
- Caller-level permissions: must be a superset of all reusable job scopes;
  missing packages: read + actions: read caused startup_failure in promote
- SHA-pinning nested reusable workflows: inner SHA must still exist;
  prefer SHAs whose nested uses: call @v1 tags not further pinned SHAs

Assisted-by: Claude Sonnet 4.5 via pi

* ci: restore testing→main promotion pipeline — parity with bluefin

* ci: restore testing→main promotion pipeline — parity with bluefin

Four changes to bring Dakota's release pipeline into parity with bluefin:

1. sync-main-to-testing.yml (new)
   After each squash-merge promotion, the squash commit lands on main but
   not in testing, leaving testing permanently behind. Without sync,
   the next promote run hits an empty squash → git commit exits 1.
   This workflow merges main back into testing on every push to main,
   keeping the cycle alive. Matches bluefin/bluefin-lts exactly.

2. renovate.json5: baseBranchPatterns main → testing
   Renovate must target testing (not main) so new dependency updates
   stage on testing first. The promote workflow then squash-merges them
   to main, which triggers execute-release to retag :testing → :stable.
   With Renovate targeting main directly, testing is a dead branch and
   the promotion PR never opens. Parity with bluefin.

3. pr-triage.yml: exempt Renovate PRs targeting testing from main gate
   The triage workflow blocks all PRs not targeting main. Renovate PRs
   to testing must be explicitly exempted or they are immediately blocked
   and cannot automerge, breaking the staging pipeline.

4. promote-testing-to-main.yml: bump actions SHA 6c2278a → efa19f2c
   Picks up 9 commits of fixes: PR body rendering, team review requests,
   e2e gate SHA correctness, source/target branch inputs. Companion fix
   for the empty-squash crash is in projectbluefin/actions PR#218.

Root cause of the original breakage: PR #741 (point Renovate at main) was
landed without adding sync-main-to-testing. After the first promotion (#797)
the cycle broke — testing fell behind, the nightly promote kept crashing
(startup_failure from missing perms, then exit-1 from empty squash).

Closes #NNN

Assisted-by: Claude Sonnet 4.5 via pi

* docs(skills): ci — testing→main promotion cycle, invariants, and failure modes

Document the three invariants required for the promotion pipeline to work,
the empty-squash crash root cause and fix, and the 2026-06-11/12 breakage
chain. Future agents: read this before touching Renovate config, branch
targets, or the pr-triage gate.

Assisted-by: Claude Sonnet 4.5 via pi

* fix(ci): use @v1 managed tag for all projectbluefin/actions refs (#830)

SHA-pinning internal org actions is counter-productive:
- fixes in projectbluefin/actions require N Renovate bump PRs to
  propagate across N repos, each lagging by hours or days
- 7 different SHAs in use across 10 workflow files meant the codebase
  was running different versions of the same actions simultaneously
- the --auto automerge bug sat broken from June 7 until tonight purely
  because the SHA hadn't been bumped in most consumers

AGENTS.md already exempts projectbluefin/ refs from SHA pinning:
  'projectbluefin/ refs (@v1, @main) are intentional managed tags
   and are exempted.'

This commit:
1. Converts all 17 projectbluefin/actions SHA refs to @v1
2. Adds a no-sha-pins-for-internal-actions pre-commit hook that blocks
   any future SHA pin to a projectbluefin/ action at commit time

The v1 tag is org-maintained and moves forward with every fix. External
action refs (actions/checkout etc.) remain SHA-pinned as required.

Assisted-by: Claude Sonnet 4.5 via pi

* fix(ci): add testing to build.yml push trigger

publish.yml already handles testing branch builds and says:
  'testing branch builds publish :testing directly so every merge to
   testing produces a fresh image. Matches bluefin/bluefin-lts behaviour.'

But build.yml only triggered on push to [main, next] — never testing.
So :testing images were never updated after any merge to testing.

Add testing to the push trigger so the publish chain fires end-to-end:
  Renovate merge → push to testing → build → publish → :testing updated

Assisted-by: Claude Sonnet 4.5 via pi

* ci(publish): speed up pipeline — skopeo promote, job split, buildah export, pip cache

P1: replace podman pull→tag→push in promote with skopeo copy --preserve-digests
    Layers never leave the registry; saves 20–25 min per variant per run.

P2: split publish into publish-image + publish-sbom; promote unblocked
    SBOM generation (10–15 min) now runs in parallel with promote.
    publish-sbom re-derives the image digest via skopeo inspect to avoid
    fragile GHA matrix job output wiring.

P3: cache buildstream-sbom pip wheel keyed to pinned GitLab commit SHA
    Saves 3–8 min pip install on cache hits. Volume-mounted into bst2 container.

P4: expand cache-warm cron from Mon/Thu to Mon–Fri
    Reduces max cold-build window from 3 days to 1 day after junction bumps.

P5: step reorder (implicit in job split — publish-image now ends with
    login/push/sign before any SBOM work)

P6: replace podman build --squash-all with buildah from+mount+sed+commit
    The squash re-encoded 8.5 GB for a 2-line sed edit. buildah commit appends
    a ~1 KB delta layer instead. chunka's BST path uses podman image mount
    (merged overlayfs) so layer count is transparent to chunkah.
    Saves 35–50 min per publish run.

Combined savings: ~65–95 min off end-to-end wall time.
Critical path to :testing drops from ~95–125 min to ~30–50 min.

Assisted-by: Claude Sonnet 4.5 via pi

* chore: promote testing to main (#824)

Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>

* docs(skills): document promotion PR approval dismissal fixes

Two patterns discovered and fixed in this session:
- force-push on unchanged squash tree dismisses approvals (actions#225)
- force-push clears reviewRequests, blocking second approval (actions#226)

Both fixes now live in reusable-promote-squash.yml@v1.

* chore(deps): update common: v2026.06-257 -> v2026.06-288 (#840)

Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>

* chore: remove standalone report recipe and OTel config duplicate (#845)

* fix(nvidia): add OpenCL, VDPAU, and suspend/hibernate support

Install libnvidia-opencl.so and nvidia.icd so host-side OpenCL works
(Blender, Darktable, etc). The ICD goes to /etc/OpenCL/vendors/ which
is the ocl-icd standard search path, confirmed by gnome-build-meta.

Install libvdpau_nvidia.so into $LIBDIR/vdpau/ — the directory was
already being created but the library was never copied in, silently
breaking VDPAU hardware video decode.

Install nvidia-hibernate/suspend/resume services and nvidia-sleep.sh
so GPU memory is saved and restored across suspend/hibernate instead
of being lost, causing hard locks on resume.

Add NVreg_PreserveVideoMemoryAllocations=1 to nvidia-modprobe-config.bst
— this kernel param is required for the suspend services to actually save
GPU memory. Without it the services run but do nothing useful. GNOME OS
had the same bug (set on nvidia_drm instead of nvidia), fixed in
gnome-build-meta!4685. Confirmed no upstream issues blocking any of
these; gnome-build-meta!4108 added the same OpenCL/VDPAU libs upstream.

Assisted-by: Claude Sonnet 4.5 via pi

* chore: remove standalone report recipe and OTel config duplicate

The ujust report recipe now ships from projectbluefin/common via
60-bonedigger.just, which dakota already inherits through common.bst.
The standalone copy in default.just and the OTel config in
files/otel/ are redundant and were behind the common version.

Remove:
- Standalone report: recipe from files/just-overrides/default.just
  (344 lines). The recipe from common's 60-bonedigger.just supersedes
  it with crash detection, PII scrubbing, journal.txt attachment, and
  smart URL routing.
- files/otel/ujust-report-config.yaml and files/otel/ directory.
  common ships this config to the image already.
- OTel overlap-whitelist entry and install-command from
  elements/bluefin/just-overrides.bst.

Note: after merging, dispatch the update-filemap workflow
(workflow_dispatch, force_regenerate: true) to remove the stale
/usr/share/ublue-os/just/otel/ujust-report-config.yaml entry from
fakecap-manifest.tsv and filemap.json.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

---------

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* fix(export): revert buildah mount+commit to podman build --squash-all (#846)

Fixes #841 — boot failure on 6/13 :testing image.

buildah commit produces multi-layer output that breaks chunka's composefs
xattr injection. Also broke local/Argo builds: quay.io/podman/stable has no
buildah (exit 127). Revert to proven podman build --squash-all.

Assisted-by: Claude Sonnet 4.5 via pi

* docs: record 2026-06-13 boot failure fix (#841)

* ci: add run-testsuite wrapper for smoke gate

* ci(publish): gate :testing behind post-publish smoke before promote

* docs: record regression test and :testing gate improvements

* fix(sbom): mkdir -p pip cache dir before podman bind-mount (#848)

On a cold actions/cache miss the ~/.cache/pip directory does not
exist on the runner. podman refuses to bind-mount a non-existent
host path and exits 125 before the container even starts, failing
the publish-sbom job every time the pip cache is cold.

Add mkdir -p ~/.cache/pip before the podman run in just sbom so
the directory is always present regardless of cache state.

Closes #853

* fix(chunkify): use largest-free tmpdir for overlay work dirs

Mirrors the same fix landed in projectbluefin/actions chunka@v1.
fakecap-restore triggers overlayfs copy-up for every file it touches;
the manifest has 700K+ entries and copy-ups can exhaust /var/tmp on
machines where root has little free space.  Pick /var/lib/containers
when it has more room (CI with BTRFS loopback), fall back to /var/tmp
otherwise (local dev).

* docs(ci): document chunka overlay disk-pressure fix and update publish pipeline diagram

- docs/ci.md: update publish pipeline to reflect current 4-job structure
  (publish-image → promote + publish-sbom in parallel); drop stale e2e-gate
  reference
- docs/skills/ci.md: split 'No space left on device' into Build vs Chunkify
  rows in the failures table; add full incident entry for the chunka overlay
  dir issue (root cause, fix location, rule against per-workflow workarounds)

* docs(skills): record actions/cache cold-miss podman bind-mount pattern

* ci(publish): add inline boot-check gate; smoke is now observational (#849)

* ci(publish): make smoke gate observational, not a promote blocker

The smoke suite (projectbluefin/testsuite) runs AT-SPI / GNOME Settings
accessibility tests that take 80+ minutes in a VM and fail on timing,
not on real image defects. This was blocking :testing promotion on
every merge and forcing manual skopeo promotions to unblock releases.

Real regressions (boot failures, composefs xattr breakage) were not
reliably caught by the slow AT-SPI tests anyway — they were caught by
user reports or by the image not booting at all.

Fix: promote runs when publish-image succeeds, regardless of smoke
result. Smoke still runs and its result is visible in the workflow
timeline for signal, but a flaky AT-SPI timeout no longer blocks the
pipeline.

The correct gate placement is:
- pre-:testing (per merge): fast boot-only check (<10 min)
- pre-:stable (weekly):     full e2e suite gates the release

Tracked in: projectbluefin/dakota#849

Assisted-by: Claude Sonnet 4.5 via pi

* ci(publish): add inline boot-check gate; smoke is now observational

Implements Option B from #850: replace the 80-min AT-SPI smoke suite
as the :testing promote gate with a fast inline QEMU boot check.

Boot-check job (hard gate, ~10 min):
- bootc installs the just-published image to a 30G raw disk
- Extracts kernel + initramfs from the ostree deployment
- Injects SSH key into the deployment
- Boots the VM with QEMU/KVM (q35, virtio-blk, hostfwd SSH :2222)
- Polls SSH until reachable (3 min timeout)
- Asserts: multi-user.target active, gdm.service active
- Uploads serial log artifact on failure for triage

Testsuite smoke job (non-blocking, observational):
- Still runs in parallel for signal
- Failure does not block :testing promotion
- Results visible in workflow timeline
- Full suite gates :testing → :stable in the weekly promotion

Promote gates on: publish-image + boot-check (hard); smoke result
is allowed to be success or failure.

Closes #850
Assisted-by: Claude Sonnet 4.5 via pi

* docs(skills): record boot-check vs smoke gate pattern (#849/#850)

* fix(ci): read OSTREE_PATH from BLS entry; detach loop device (#852)

Two bugs in the new boot-check job:

1. OSTREE_PATH was constructed as
   /ostree/deploy/default/deploy/${DEPLOY} — wrong format.
   The kernel arg requires /ostree/boot.1/default/TREEHASH/N
   where TREEHASH is the ostree commit SHA, a different value
   from the deploy directory name. Result: VM would fail to
   switch-root and hang on boot.

   Fix: mount the boot partition (p2) and read the exact path
   from the BLS entry (loader/entries/*.conf options line).
   This is guaranteed to be correct — it is what the bootloader
   itself would use.

2. The loop device was never detached after unmounting /mnt,
   leaving it dangling while QEMU held disk.raw open.

   Fix: export LOOP as a step output and run
   losetup -d at the end of the Inject step.

Assisted-by: Claude Sonnet 4.5 via pi

* docs(skills): record OSTREE_PATH BLS entry pattern for boot-check (#852)

* chore(deps): update common: v2026.06-288 -> v2026.06-311 (#855)

Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>

* fix(nvidia): add OpenCL, VDPAU, and suspend/hibernate support (#837)

Install libnvidia-opencl.so and nvidia.icd so host-side OpenCL works
(Blender, Darktable, etc). The ICD goes to /etc/OpenCL/vendors/ which
is the ocl-icd standard search path, confirmed by gnome-build-meta.

Install libvdpau_nvidia.so into $LIBDIR/vdpau/ — the directory was
already being created but the library was never copied in, silently
breaking VDPAU hardware video decode.

Install nvidia-hibernate/suspend/resume services and nvidia-sleep.sh
so GPU memory is saved and restored across suspend/hibernate instead
of being lost, causing hard locks on resume.

Add NVreg_PreserveVideoMemoryAllocations=1 to nvidia-modprobe-config.bst
— this kernel param is required for the suspend services to actually save
GPU memory. Without it the services run but do nothing useful. GNOME OS
had the same bug (set on nvidia_drm instead of nvidia), fixed in
gnome-build-meta!4685. Confirmed no upstream issues blocking any of
these; gnome-build-meta!4108 added the same OpenCL/VDPAU libs upstream.

Assisted-by: Claude Sonnet 4.5 via pi

* feat(fonts): remove jetbrains-mono from image, manage via Homebrew

Remove jetbrains-mono.bst and jetbrains-mono-nerd-font.bst from the
image dependency tree. Both fonts are available to users via the
Homebrew fonts.Brewfile (font-jetbrains-mono-nerd-font cask), which
system.just already prompts to install during setup.

Aligns with the broader effort to get fonts off OCI images and manage
them entirely through Homebrew across all Bluefin variants.

Assisted-by: Claude Sonnet 4.6 via pi

* feat(brew): move glow and fzf to brew-preinstall

Part of the image diet work from projectbluefin/common#546.
Depends on projectbluefin/common#664 (brew-preinstall service).

Remove glow.bst and fzf.bst elements and their entries from deps.bst.
Both are now installed at first login via common's system-cli.Brewfile
by brew-preinstall.service.

uutils-coreutils.bst stays — Dakota ships uutils natively via
BuildStream as a deliberate design choice.

Assisted-by: Claude Sonnet 4.5 via pi

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* fix(bootc): require xfs root filesystem in install config

bootc no longer defaults the root filesystem type when installing to
disk. Without [install.filesystem.root] type = "xfs", bootc exits 1
with "No root filesystem specified", leaving disk.raw unpartitioned.
The CI boot-check then fails at losetup -P because there are no
partition nodes to mount.

The boot-check gate has been failing since it was introduced (PR #849,
2026-06-13) for this reason — never once passed. This is the fix.

No behaviour change for users installing to real hardware; xfs was
always the implicit default. This makes it explicit.

Assisted-by: Claude Sonnet 4.5 via pi

* ci(pr): enable auto-merge after approval; add pr-autoupdate workflow (#858)

After a maintainer submits a GitHub APPROVE review, pr-triage now:
  1. Removes pr/needs-review (existing)
  2. Enables auto-merge via --auto --squash (new)
  3. Updates the branch so CI runs against current main (new)

New pr-autoupdate.yml fires on every push to main and calls
gh pr update-branch on all open non-bot PRs with mergeStateStatus=BEHIND.
This prevents auto-merge from being blocked by stale branches.

Requires 'validate' in branch protection required_status_checks so
--auto waits for CI before merging (separate API change).

Closes the gap where 'Approved. Auto-merge is now eligible' was posted
but auto-merge was never actually enabled.

Assisted-by: Claude Sonnet 4.5 via pi

* fix(ci): pre-create host loop device for boot-check; drop --via-loopback

--via-loopback creates the loopback device inside the container. The host
kernel then creates partition device nodes (loop0p2, loop0p3) asynchronously
via BLKRRPART, but they're not visible inside the container in time for
bootc's mkfs call. Result: 'Creating rootfs: No such file or directory'.

Fix: fallocate + losetup on the HOST before the container starts, then
pass the real block device path ($LOOP) to bootc instead of --via-loopback.
The host kernel owns the partition nodes, which appear inside the container
via the -v /dev:/dev bind mount. Follow with partprobe + udevadm settle to
ensure nodes are ready before the extract step.

Also pass the loop device name via GITHUB_ENV (BOOT_CHECK_LOOP) so the
extract step reuses it rather than creating a second attachment to disk.raw.

Corrects the disk layout comment: bootc with systemd-boot creates
p1=BIOS boot, p2=EFI/xbootldr (BLS entries here), p3=Linux root (xfs).

The boot-check gate has never passed since it was introduced (#849).
This is the second part of the fix; the first was requiring xfs in the
install config (#859).

Assisted-by: Claude Sonnet 4.5 via pi

* docs(skills): record testing pipeline and boot-check fixes (2026-06-14)

Lessons learned from the 2026-06-14 pipeline unblock session:

ci.md:
- Fix wrong partition layout comment: p1=BIOS boot, p2=EFI/xbootldr,
  p3=Linux root (xfs). Was incorrectly documented as p1=EFI, p2=/boot, p3=/.
- Add: testing branch divergence breaks sync permanently — emergency
  reset procedure + systemic fix (force-reset when ahead > 0, PR #237)
- Add: pr-triage approval gap — 'auto-merge eligible' comment never
  actually enabled auto-merge; fixed in PR #858 with --auto + update-branch
  + new pr-autoupdate.yml + validate required check on branch protection
- Add: bootc 'No root filesystem specified' — [install.filesystem.root]
  type = 'xfs' now required in 00-defaults.toml (PR #859)
- Add: bootc --via-loopback partition nodes invisible in container —
  pre-create loop on HOST, pass $LOOP directly, add partprobe/udevadm
  settle (PR #864). Boot-check gate never passed since PR #849.

IMPROVEMENTS.md:
- Record pipeline unblock and boot-check double fix

* fix(ci): switch bonedigger caller to @main — eliminate SHA drift (#875)

SHA pins on internal projectbluefin/ workflow refs silently break
when they drift. The current pin (30a240ea, June 11) is 4 days
behind bonedigger HEAD.

Internal org refs don't need SHA pinning. Use @main so the caller
is always current without manual bumps or Renovate churn.

Assisted-by: Claude Sonnet 4.5 via pi

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* fix(ci): also exempt projectbluefin/bonedigger from floating-tag hook (#878)

* docs(ci): document run_e2e=false design, narrow hook exemptions to actions only

- promote-testing-to-main.yml: add inline comment explaining that
  run_e2e: false is intentional — the e2e quality gate lives at the
  weekly-testing-promotion.yml level with 2 human approvals in the
  production Environment, not at the PR gate level.
- AGENTS.md: add explicit 'Dakota promotion PR has no e2e gate by design'
  note to prevent agents from treating run_e2e: false as a bug.
  Narrow pre-commit guard and SHA pinning notes to projectbluefin/actions.
- .pre-commit-config.yaml: narrow no-floating-action-tags and
  no-sha-pins-for-internal-actions to projectbluefin/actions/ only.

Assisted-by: Claude Sonnet 4.5 via pi
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* fix(ci): also exempt projectbluefin/bonedigger from floating-tag hook

bonedigger has a managed @v1 tag. Extend exemption to cover both
projectbluefin/actions and projectbluefin/bonedigger.

Assisted-by: Claude Sonnet 4.5 via pi
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* ci: retrigger checks after base retarget to main

---------

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* fix(ci): use losetup -P (PARTSCAN) so bootc can find partition nodes (#883)

* docs(ci): document run_e2e=false design, narrow hook exemptions to actions only

- promote-testing-to-main.yml: add inline comment explaining that
  run_e2e: false is intentional — the e2e quality gate lives at the
  weekly-testing-promotion.yml level with 2 human approvals in the
  production Environment, not at the PR gate level.
- AGENTS.md: add explicit 'Dakota promotion PR has no e2e gate by design'
  note to prevent agents from treating run_e2e: false as a bug.
  Narrow pre-commit guard and SHA pinning notes to projectbluefin/actions.
- .pre-commit-config.yaml: narrow no-floating-action-tags and
  no-sha-pins-for-internal-actions to projectbluefin/actions/ only.

Assisted-by: Claude Sonnet 4.5 via pi
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* fix(ci): also exempt projectbluefin/bonedigger from floating-tag hook

bonedigger has a managed @v1 tag. Extend exemption to cover both
projectbluefin/actions and projectbluefin/bonedigger.

Assisted-by: Claude Sonnet 4.5 via pi
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* ci: retrigger checks after base retarget to main

* fix(ci): use losetup -P (PARTSCAN) so bootc can find partition nodes (#884)

Without LO_FLAGS_PARTSCAN, ioctl(BLKRRPART) returns EINVAL on the host-side
loop device and the kernel never creates /dev/loop0p3 device nodes inside the
container. bootc's internal sfdisk writes the partition table but immediately
tries mkfs on a nonexistent node, failing with "Device has no children".
The || echo swallows the error, leaving p3 with no filesystem. The Extract
step then fails with the confusing "wrong fs type" message.

Fix: add -P to losetup so LO_FLAGS_PARTSCAN is set. The kernel then
auto-creates partition nodes via uevents when sfdisk writes the table inside
the container. The container sees them via -v /dev:/dev.

Also adds a post-install blkid check to fail fast with a clear diagnostic
instead …
@castrojo castrojo added 4-review A pull request is awaiting review. and removed area/ci labels Jul 29, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

4-review A pull request is awaiting review.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant