BootUI
Try it
Setup
Features
Workshop
Properties
AI agents
Ecosystem
GitHub
Try it
Setup
Features
Workshop
Properties
AI agents
Ecosystem
GitHub
  • Get started

    • Try the sample app
    • Setup
    • Spring WebFlux
    • Quarkus
    • Java agent
    • Activation and safety
    • Non-standard runtimes
    • Troubleshooting
  • Workshop

    • Workshop overview
    • 00 - Runtime understanding
    • 01 - Setup and tooling
    • 02 - Configuration and wiring
    • 03 - Runtime journal
    • 04 - Runtime Insights
    • 05 - Java instrumentation
    • 06 - Assessment and impact
    • 07 - Agent change loop
    • 08 - Going further
    • Participant worksheet
    • A - Prompts
    • B - Troubleshooting
    • C - Frameworks
    • D - Extensions
  • Features

    • All features
    • Scorecard
    • Live Activity
    • Runtime Insights
    • Advisors
    • Runtime
    • Configuration
    • Database
    • Security
    • Services
    • Diagnostics
    • Java Agent
    • Developer tools
  • Reference

    • Properties
    • Framework support
    • AI agents
    • Command line
    • BootUI family
    • Known limitations
  • Diagnostic checks

    • Architecture
    • REST API
    • Spring
    • Hibernate
    • Database
    • Security
    • Vulnerabilities
    • Memory
    • Pentesting
    • GraalVM readiness
    • CRaC readiness
    • Quarkus
    • Quarkus security
  • Contributing

    • Repository
    • Specification
    • Implementation plan
    • v2 plan
    • Try 2.0 early
    • v2 validation report
    • Releasing 2.0
    • Quarkus design notes
    • WebFlux design notes
  • Privacy

Releasing 2.0

This is the maintainer runbook for the 2.0.0 release path (v2 plan §4.3, M4-23): how merging v2 into main is kept from publishing anything early, how that is rehearsed, how the 1.x maintenance branch is cut and released from (D40), and the order of the release day. The Release workflow itself, its signed tags, and its recovery paths are described in CONTRIBUTING.

Nothing is published from v2 before 2.0.0 (D5). v2 still carries the 1.x project version, so after the merge the project version on main cannot tell 2.0 contents from 1.x contents. The release line can.

The release line

Every branch declares its release line, the major version its contents belong to, in .github/release-line: one number, with # comments. v2 declares 2; the prepared main declares 1, and 1.x will inherit 1 when it is cut. The line must be the project version's major, or that major plus one while a branch prepares the next major, and a missing or malformed file fails every check that reads it. A branch containing bootui-agent, which exists only from 2.0, must declare at least 2, so a main-to-v2 sync that resolves a conflict on this file to 1 fails the integrity guard, the gate, and the Release workflow instead of letting the merged main publish 2.0 as the 1.x line. The first commit on a branch preparing a later major, such as v3, declares that major.

How each channel is guarded

ChannelWhat decidesBefore the mergeAfter the merge, before 2.0.0Once 2.0.0 is on Maven Central
Documentation site (pages.yml)release-line-gate.sh on every push and manual runmain (line 1) deploysmain (line 2) builds the site but skips the upload and the deploymain deploys again; 1.x never deploys
Docker Hub sample images (docker-publish.yml)The same gate, in the workflow's first jobmain publishes dailyEvery job but prune is skippedmain publishes 2.x images; 1.x publishes none
Maven Central (release.yml)release-version-policy.sh with the release line, before any file changes, and again on the tagged contents before publication; only from main or the version's N.x branchmain releases 1.x onlymain releases 2.0.0 only, and only when dispatched; 1.x releases 1.x patches onlymain releases 2.x; 1.x releases 1.x patches, without redeploying the site
JBang alias (jbang-catalog.json)The Release workflow rewrites it1.xUnchanged: the catalog is identical on main and v22.0.0
Installers (install.sh, install.ps1) and the CLI's update checkMaven Central's maven-metadata.xml1.x1.x2.x

release-line-gate.sh reads the branch's release line, the tags on origin, and Maven Central. A branch may publish only when one tag of its line has its bootui-engine and Spring starter jars on Maven Central (2.0 no longer publishes the parent POM or bootui-core), and no tag of a newer major does. Requiring any tag, not the newest, keeps one failed or still-propagating patch from blocking every later deploy, and counting a newer major only once it is on Maven Central keeps a stray or failed tag from retiring the released line. An unreadable answer, from origin or from Maven Central, fails the run instead of deciding.

The Release workflow dispatches pages.yml at the release tag only after every artifact is available and only for the newest major, so the gate passes there, and it fails unless that run's deploy job actually deployed. Pull requests still build the site, without the gate.

The Release workflow also prepares a release only from main or from the version's maintenance branch (1.x for a 1.x version), and publishes a tag only when its commit is on one of them, so a signed tag pushed on v2 cannot publish before the merge.

check-release-integrity.sh pins all of this: the release-line arguments, the tagged-contents and release-branch checks, and the deploy confirmation in release.yml; the gate and the gated upload and deploy in pages.yml; the gate job ahead of every publishing job in docker-publish.yml, whose image jobs keep exactly their repository condition so no status function such as always() can run them after a skipped gate; and a valid .github/release-line. Workflows may not set the gate's test seams.

What workflow files cannot guard

A manual run executes the workflow file of the ref it is dispatched from, so refs created before the gate, such as the v1.* tags or an older v2 feature branch, carry ungated workflows forever. The GitHub environments are the only control over them:

  • maven-central accepts main and v* tags, with required reviewers. It refuses manual runs from v2 and its feature branches. Add 1.x when the branch is cut.
  • github-pages accepts main and v* tags. Right after the 2.0 site deploys, narrow the tag rule to v2.*, so an old tag's ungated pages.yml cannot replace it. Repeat at 3.0.
  • docker-hub accepts only main (done 2026-10-04). Without that policy, any ref whose docker-publish.yml predates the gate, such as an older v2 feature branch or a v1.* tag after 2.0.0, could push latest through a manual run. The daily schedule runs only on main, so nothing legitimate lost access. A manual run from any other ref now fails at its first docker-hub job, including a prune_dry_run preview: run retention previews from main. Keep this policy at 3.0.

Re-running a workflow run replays the workflow file of that run, and GitHub allows it for 30 days. Never re-run a Pages or Docker run that predates the gate on main; the preparation below has landed, but old runs remain a concern until they age out.

The rehearsal

.github/scripts/rehearse_v2_merge.py rehearses the merge on a candidate that is never published. It builds the merge commit of main and v2 with git merge-tree and git commit-tree, so no branch moves, and checks:

  • every workflow that a push to main or a schedule runs, and that can publish, is behind the gate, and the Release workflow has no branch trigger;
  • on the candidate, the integrity guard passes, the gate decides not to publish the site or the images, against the real tags on origin and Maven Central, and the Release workflow's policy refuses the next 1.x patch and accepts only 2.0.0;
  • the 1.x line before the merge still publishes, against the real tags and Maven Central;
  • simulated futures, with synthetic tags and a local stand-in for Maven Central: a v2.0.0 tag without its artifacts publishes nothing, its artifacts publish the 2.x site, and the 1.x branch then publishes no site, releases its next patch without redeploying the site, and can never release 2.0.0;
  • whether main is ready to become 1.x, with its own integrity guard and release tests passing, whether the release sign-off explicitly approves 2.0.0 with complete candidate and maintainer metadata, and whether the GitHub environments are set as above (read only, with gh).
# Any day: a local, read-only rehearsal against origin/main and the current v2 commit
python3 .github/scripts/rehearse_v2_merge.py --v2 origin/v2

# Release day, only after the approved 1.x cut and administrator settings:
# also run the gated workflows on GitHub, and fail on any pending prerequisite
python3 .github/scripts/rehearse_v2_merge.py --v2 origin/v2 --live --release-day

--live pushes the candidate to a temporary rehearsal/v2-merge-* branch, dispatches pages.yml and docker-publish.yml from it, requires every publishing job to be skipped, and deletes the branch. No push trigger runs on that branch, and the github-pages and maven-central environments refuse it, so a broken gate fails rather than publishes; docker-hub refuses it too, so the Docker image jobs could not push even past a broken gate. As a second line, the rehearsal cancels a run as soon as any job past its gate is queued. It never pushes the temporary branch or dispatches either workflow while any prerequisite check is FAIL or PENDING, including incomplete or non-approved release sign-off.

A merge conflict fails the rehearsal: merge main into v2 first. Prerequisites that are not done yet are reported as PENDING; --release-day turns them into failures.

The read-only rehearsal also runs main's complete release-policy test suite. On macOS this can take several minutes: progress remains captured until the suite exits, so a short external timeout can look like a stalled test. An interrupted rehearsal is incomplete, not a passing preflight or proof of a failed assertion. If an external timeout stopped it, preserve the log, distinguish an actual assertion failure from the timeout, and rerun the same read-only command with enough time for the full suite. Do not skip the maintenance-line tests or weaken a guard to get past a timeout.

Showcase video

The homepage MP4 lives outside Git as bootui-showcase.mp4 on a media-only GitHub Release. Each branch selects that release in docs/.vuepress/showcase-video.json. main is the 2.0 line and selects showcase-video-v2-r4, with its matching poster, captions, and player copy. The earlier media releases (showcase-video, showcase-video-v2, showcase-video-v2-r2) remain hosted but are not selected. Keep all published media releases and assets intact. These media tags do not start with v, so they do not trigger the Maven Release workflow or count as a product version. A new product release does not require changing the showcase video.

Published media releases can be immutable. If an approved correction is needed, publish a new revisioned media release, such as showcase-video-v2-r2, and update the manifest after verifying its assets. Do not delete or move the previous media tag or overwrite immutable assets. A superseded render is not the final approved video.

Upload only the verified final render. Commit its matching JPEG poster and English WebVTT captions at docs/.vuepress/public/videos/bootui-showcase-poster.jpg and bootui-showcase.en.vtt on the corresponding documentation branch; do not pair the new narration with old captions. Pages downloads the selected MP4 during the site build, fails on a missing config or asset, and checks all three assets in the generated site.

Release sign-off format

The maintainer records the decision in the single Release sign-off section of the validation report. Each required table row appears exactly once:

FieldRequired format
Release candidateA full lowercase 40-character commit SHA and its BootUI version: `<SHA>` (version `<major.minor.patch>`). Identify the final validated source, not a historical rerun build.
DecisionExactly PENDING, HOLD, or APPROVE_RELEASE_2_0_0, with no prose or qualifiers in the cell. Only APPROVE_RELEASE_2_0_0 authorizes release.
Decision rationaleThe maintainer's nonempty rationale, including accepted exceptions or reasons for holding release.
MaintainerIn the Role, Name, Date table: a nonempty, non-placeholder name and a valid, non-future calendar date in YYYY-MM-DD format.

Empty or missing fields, duplicate rows, placeholders, invalid dates, and unresolved TODOs never authorize release. Free-form decisions such as "Do not release", a question, or a conditional approval also never authorize it; record the exact decision token and put its explanation in Decision rationale. Ordinary read-only rehearsal reports incomplete or non-approved sign-off as PENDING, not PASS; --release-day reports it as FAIL. This is validation of the recorded human sign-off, not cryptographic authentication or a substitute for maintainer review of the source and evidence. Agents must never fill in an approval, candidate, name, or date on the maintainer's behalf.

The 1.x maintenance branch

1.x is cut from main's last 1.x commit right before v2 merges. Until then main is the 1.x line and keeps receiving fixes; do not cut the branch early.

Before the cut: verify the prepared main

The per-major release machinery and release-line gates have landed on main. The following is the preparation record, not an outstanding instruction to backport them again. Before release day, verify that subsequent changes have kept these invariants; the read-only rehearsal checks them:

  1. release-version-policy.sh, release-line-gate.sh, pages.yml, and docker-publish.yml are byte-identical on main and v2. Preserve that equality through reviewed changes whenever a shared gate changes. The gate's sentinel is bootui-engine, not the retired 2.0 bootui-core coordinate, so the prepared 1.x line recognizes 2.0 on Maven Central.
  2. main's release.yml has the release-line arguments, the tagged-contents and release-branch checks, the deploy confirmation, and the newest-major documentation decision, keeping main's own publication reactor and availability list, which have no agent modules; its integrity guard and release tests enforce that 1.x-specific machinery.
  3. main's .github/release-line is 1. When syncing it into v2, keep 2 on v2 (the guard rejects 1 on a branch containing bootui-agent).
  4. 1.x is listed in the push and pull-request branches of build.yml and jdk-compatibility.yml, so it gets the green build a release requires.

This preparation and a read-only rehearsal are not release authorization. Cutting 1.x, merging the release, dispatching live rehearsals or Release, signing, and publishing still require the maintainer's release-day decision; environment/ruleset changes require a repository administrator. Pending sign-off or environment permissions remain blockers, not waived checks.

Settings, by a repository administrator

  • A ruleset for 1.x like main's: no force push or deletion, pull requests required, and github-actions[bot] allowed to push the release commit and its v1.* tag.
  • The maven-central environment accepts the 1.x branch.
  • The docker-hub environment already accepts only main (done 2026-10-04); nothing to change for 1.x, which publishes no images.

Cutting the branch

On release day, after the last 1.x release from main, the sync into v2, and the read-only preflight, and only with explicit maintainer approval, freeze main and cut the branch. The administrator applies the settings above before the strict live rehearsal; that rehearsal requires maven-central to accept 1.x, so it cannot precede the cut and settings. Keep main frozen from preflight through the cut, strict rehearsal, and v2 merge:

git fetch origin
git show origin/main:.github/release-line   # must declare 1
git push origin origin/main:refs/heads/1.x

The branch points at the frozen, preflighted main commit: no merge, no rebase, no new commit. Stop if main advances; do not move an already-cut maintenance branch to repair the sequence. Reconcile the source and repeat preflight before proceeding.

Releasing a 1.x patch

  1. Fix on 1.x through a pull request into 1.x, and port the fix to main when it applies to 2.x (main-to-v2 syncs carry it into any later major's branch).
  2. Cut CHANGELOG.md on 1.x to ## [1.N.P] - YYYY-MM-DD as its own commit, and wait for a green build.
  3. Run Release from 1.x with the next 1.x version. The policy accepts only the next patch or minor after the newest 1.x tag; the release line refuses 2.x versions.
  4. After 2.0.0, the workflow skips the documentation redeploy, with a notice: the site follows the newest major. Between the merge and 2.0.0, 1.x is still the newest released major, so a 1.x patch still redeploys the 1.x site from its tag. A manual pages.yml run from 1.x is refused by the github-pages environment, which accepts only main and tags.

Release day

  1. Review. Known limitations matches the shipped scope. Review the release sign-off evidence and accepted exceptions; a pending or hold decision is not release approval.
  2. Last 1.x release. Release any unreleased 1.x change from main as usual, and verify the prepared main above.
  3. Sync. Merge main into v2 with a merge commit, never a rebase; .github/release-line stays 2.
  4. Read-only preflight. Freeze main, then run python3 .github/scripts/rehearse_v2_merge.py --v2 origin/v2 without --live or --release-day. Resolve every failure. Missing 1.x permission and unsigned sign-off may still be PENDING at this stage; record them as blockers, not waived checks. The maintainer identifies the final validated candidate and records its explicit approval, rationale, name, and date in the supported format before authorizing the next step.
  5. Cut 1.x and apply its settings. With explicit maintainer approval, cut from the frozen, preflighted origin/main, as above. A repository administrator applies the 1.x ruleset and maven-central branch permission. Do not waive either protection to make the rehearsal pass; main remains frozen.
  6. Strict live rehearsal. Only after the cut and settings, with explicit maintainer authorization, run python3 .github/scripts/rehearse_v2_merge.py --v2 origin/v2 --live --release-day. It must report no failure and nothing pending before the merge. If it fails, stop and fix the prerequisite; do not advance main.
  7. Merge v2 into main with a merge commit. Check that the Pages run of the merge push skipped its upload and deploy with "release line 2 has no release tag yet", and that the next Docker run skipped every job but prune.
  8. Changelog. Cut CHANGELOG.md's [Unreleased] to ## [2.0.0] - YYYY-MM-DD on main, as its own commit, and wait for a green build.
  9. Release. With maintainer approval, run Release from main with version 2.0.0. Merge nothing into main until the run is green: while Maven Central propagates, a push could find the gate's two artifacts before the others. The run refuses an imported key other than the pinned RELEASE_KEY_FINGERPRINT (7B7C0BD038603E5A9F1476D0498BA5AC9BABBAF9), and checks every signature against it twice: after the verification build, before the tag (unless skip_build), and on the Central bundle, before the upload. Normal preparation also stages the exact candidate bundle and smoke-tests its consumers before committing/tagging. Tag-entry and skip_build runs do that from the immutable tagged checkout before uploading instead; a successful pre-tag smoke is not repeated after the tag. Publication, availability checks, published-consumer smoke and the site deployment all use the commit peeled from the verified signed tag.
  10. After the release. Confirm that the site shows 2.0 (deployed from the v2.0.0 tag), and that jbang bootui@jdubois/boot-ui and the installers resolve 2.0.0. Once 2.0.0 is available on Maven Central, explicitly run Publish Docker images (docker-publish.yml) from main, with cleanup_only=false, rather than waiting for the daily schedule. Before Central availability, the gate must still skip the build and publication; never bypass it to test new images early. Require all six variants (JVM, AOT, CRaC, native, WebFlux, and Quarkus) on both architectures to pass the workflow's native-runner smoke tests, including readiness, BootUI API/UI, AOT advisor checks, and CRaC checkpoint/restore. Verify the published multi-arch manifests and exact tested digests, then pull and exercise the published images on available platforms; report any platform not additionally exercised rather than claiming local cross-architecture coverage. Narrow the github-pages tag rule to v2.*. After the first 1.x patch that follows, confirm that Maven Central's maven-metadata.xml for bootui-cli still lists a 2.x <release>, which the installers and the CLI's update check read.
Prev
v2 validation report
Next
Quarkus design notes

© 2026 Julien Dubois