Skip to content

ci: release the runtime that the checks built when it lands - #5

Merged
martinothamar merged 1 commit into
main-digdirfrom
ci/release-tag-on-publish
Oct 25, 2026
Merged

martinothamar merged 1 commit into
main-digdirfrom
ci/release-tag-on-publish

Conversation

@martinothamar

@martinothamar martinothamar commented Oct 25, 2026 •

Copy link
Copy Markdown

Summary

Part of digdir/digdir-agents#279.

What changes: the runtime is built once, in the pull request's checks, and landing that commit on main-digdir publishes exactly those files. Nobody runs a release workflow or pushes a tag by hand.

How a release works today (MAINTAINING-digdir.md, step 6)

  1. check-digdir.yml builds the runtime with the CI profile (opt-level 1, no LTO) and runs the KVM integration and smoke tests against it.
  2. Run check-digdir.yml on main-digdir again and wait for it to pass.
  3. Run release-digdir.yml on main-digdir, a dry run that builds the runtime again, this time with the release profile, and validates it.
  4. Tag the libkrunfw commit.
  5. Push the tag v<version> by hand. That starts release-digdir.yml a third time, which builds the runtime once more and publishes it.

So a release builds the runtime three times, the published runtime is never the one the tests ran against, and a step 5 failure that needs a code fix spends the version.

How a release works after this PR

  1. The pull request's checks build the runtime once, with the release profile, and run the KVM integration and smoke tests against those binaries. Then they assemble the release files: the bundles, libraries, guest agents, licenses, third-party notices, firmware source and checksums.sha256. The run keeps them as an artifact named release-<tree>, after the tree it built: for a pull request, its merge with main-digdir as it was when the run started.

    • A sync branch gets no pull_request checks, so start them with gh workflow run check-digdir.yml --ref sync/digdir-X.Y.Z, as today.
    • If main-digdir moves before a pull request merges, rebase the pull request onto it and force-push, so that its checks build the tree that will land.
  2. Tag the libkrunfw commit, as today.

  3. Land it. Which way depends on the change:

    • A synchronization lands by moving main-digdir to the approved sync branch: git push --force-with-lease=main-digdir:<old-tip> origin sync/digdir-X.Y.Z:main-digdir. Never use its PR's merge button, which would replay the rebuilt queue onto the old main-digdir. Once main-digdir points at the sync branch's head, GitHub marks the PR as merged by itself. Don't close or merge it.
    • Any other change lands through its PR's merge button, which rebase-merges it.

    Either way, release-digdir.yml runs on the push. It releases only if the Cargo.toml version has no release yet, which is true only for a version bump: the last commit of a synchronization (X.Y.Z-digdir.1), or a PR that bumps -digdir.<n> between synchronizations. For a version bump, it:

    1. refuses, creating nothing, if libkrunfw is untagged, or if no successful check run in this repository built exactly this tree
    2. takes the oldest successful check run that did, and stops if that run hasn't finished. The bundles aren't byte-reproducible, so a later run of the same tree has other digests, and the oldest is the one a consumer could already have pinned.
    3. downloads that run's release files and verifies their checksums
    4. creates a draft release on the landed commit, uploads the files, checks that nobody has pushed the tag meanwhile, and publishes it, which creates v<version>

    A push whose version is already released publishes nothing.

What this gets us

  • The runtime is built once per release instead of three times. Releasing after landing takes a few minutes instead of about 35.
  • The published runtime is the one the integration and smoke tests ran against. On Windows, the shipped binary gets the runtime-discovery smoke test, as before.
  • The bundle digests that digdir-agents pins are final as soon as the sync branch's checks pass, before anything lands. The bump PR can use them while the sync is still in review.
  • Nothing is built after landing, so nothing can fail there that a code change would have to fix, and no version is lost. If the release refuses, fix the cause (tag libkrunfw, or run the checks on main-digdir) and run release-digdir.yml again.
  • The upstream-base check now runs with the other checks, before landing, not 30 minutes into a release.

Other changes:

  • The firmware source archive is named by its libkrunfw commit instead of its tag, so the files are final before libkrunfw is tagged. The release notes still name the tag.
  • The third-party notices are generated in their own job, alongside the builds.
  • On Windows the test job no longer builds its own msb. The new build job builds the shipped one and smoke-tests it.
  • The release profile builds more slowly than the CI profile: the first run took 37 minutes, against about 28.5 for the last CI-profile run. The release builds now have their own caches.

After merging:

  • Run gh workflow run check-digdir.yml --ref main-digdir once. The release builds use new caches, which only runs on main-digdir save, so until then pull requests build without them. The runbook's step 4 now says to do this after any cache key change.
  • Make "Assemble downstream runtime release" a required check in the main-digdir ruleset, so that a pull request can't merge before its release files exist.

Evidence

  • The checks on an earlier head, e33b1fa8, the first run of the single build, passed in 37 minutes.
  • The checks on 7de216c2, the final design apart from later doc wording, passed in 36 minutes. The integration and smoke tests ran against the release-profile binaries. The notices job ran alongside the builds, so assembly took under a minute instead of 5. The run kept the release files as release-700a0e3e…, the PR's tree.
  • The release's lookup, run read-only against that artifact, selected that run. The same lookup passes over the old release workflow's dry runs.
  • Merging this PR is the first push the new release-digdir.yml sees. 0.7.8-digdir.2 is already released, so that run should publish nothing.

Risk

  • The publish steps only run on the next release. A failure before publishing leaves at most an unpublished draft, which the next run deletes.
  • Release tags become plain tags created by github-actions[bot].

martinothamar added a commit that referenced this pull request Oct 25, 2026
udevadm trigger returns before udev has applied the rule that opens
/dev/kvm to the runner user, so the check right after it can fail on a
runner that has KVM. The CLI smoke tests failed this way three times in a
row on #5, while the integration tests, with the same
step, passed. Wait for udev to settle, and list the device so that a
runner without KVM shows as one.
@martinothamar
martinothamar force-pushed the ci/release-tag-on-publish branch from 534d37c to e19435e Compare October 25, 2026 10:50
@martinothamar
martinothamar marked this pull request as ready for review October 25, 2026 10:51
@martinothamar
martinothamar force-pushed the ci/release-tag-on-publish branch 2 times, most recently from 4494b5a to e33b1fa Compare October 25, 2026 12:07
@martinothamar martinothamar changed the title ci: create the runtime release tag when the release is published ci: release the runtime that the checks built when it lands Oct 25, 2026
@martinothamar
martinothamar force-pushed the ci/release-tag-on-publish branch from e33b1fa to 7de216c Compare October 25, 2026 13:18
check-digdir.yml now builds the runtime once, with the release profile,
runs its tests against those binaries, and assembles them into the
release assets, which the run keeps as an artifact. Before, the checks
tested a CI-profile build and the release built the runtime again from
the tag, so what was published was never what was tested.

release-digdir.yml runs when main-digdir moves to a version that has no
release yet. It builds nothing: it finds the release assets by the tree
that landed, which check-digdir.yml names them by, takes the oldest
successful run in this repository, verifies its checksums and publishes
them, creating the v<version> tag on the landed commit as it publishes
the release. The oldest run is the one whose digests a consumer could
have pinned, since the bundles are not byte-reproducible. A failed run
spends no version and can run again. It refuses when libkrunfw is
untagged or no check run built the tree.
The manual dry run, the check and dry run on main-digdir before a
release, and the hand-pushed tag are gone.

The bundle digests are final once the sync branch's checks pass, so a
consumer can pin them before the release. The firmware source archive
is named by its libkrunfw commit for that reason; the notes name the
tag. The upstream base is checked with the other checks, before
landing, and the third-party notices are generated alongside the
builds.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant