Release Workflow

Version 1.7.1 | Updated: October 07, 2026

Release Workflow

Status: in force from 1.7.1 (DEV-3006, 2026-10-07). This repository is released by the CI/CD Manager, the same way as every other Friendly Technologies project with a dev branch (northbound-api and the rest). Releases up to and including 1.7.0 were made with misc/release.sh; see Before 1.7.1 for what that left behind.

At a glance

   feature/*, bugfix/*   --(QA on the branch, then PR)-->   develop
                                                               |
                                    CI/CD Manager: Finalize Sprint
                                    (version + changelog on develop,
                                     PR develop -> main)
                                                               |
                                                     merge commit into
                                                               v
                                                             main  ---- tag release-X.Y.Z
                                                               |
                                                               +--> release/X.Y.Z   (created from main,
                                                                                     kept for good)
                                                     develop moves to X.Y.(Z+1)
  • develop is the release candidate. It holds only work that has already been tested and that goes into the next release.

  • main holds the latest release and every release before it. The installer installs main by default.

  • release/X.Y.Z is cut from main when version X.Y.Z is released. It is the place where version X.Y.Z lives on: the docs portal publishes the X.Y.Z documentation from it, and a hotfix for X.Y.Z goes there.

  • Tags: release-X.Y.Z, on the main commit the release was made from.

Branches

Branch Created from Purpose Lifetime

main

—

The latest release and all releases before it. Every release tag points at a commit on main. The default install ref.

Permanent

develop

main (once)

The release candidate: tested features and fixes for the next release.

Permanent

feature/*

develop

New functionality.

Deleted after merge

bugfix/*

develop

Fixes for the next release.

Deleted after merge

release/X.Y.Z

main, by the CI/CD Manager, at the release

Version X.Y.Z after its release: published docs, hotfixes for that version.

Kept for good — deleting it unpublishes that version’s docs

hotfix/*

release/X.Y.Z

A fix for an already released version.

Deleted after merge

Only the CI/CD Manager creates release/X.Y.Z branches. Do not push any other branch whose name starts with release/: the docs portal publishes every release/* branch, and two branches that carry the same version stop the whole portal build.

Feature and bugfix workflow

  1. Branch feature/<DEV-xxxx>-<slug> or bugfix/<DEV-xxxx>-<slug> from develop.

  2. Commit using Conventional Commits with the Jira key in the subject — see Commits and release notes.

  3. QA tests the branch: install it on a test server with bootstrap.sh --branch <your branch>. On any branch other than main and release/* the installer runs that branch’s own builds of Config UI and Trap Catcher (see Our own images), so QA needs no manual .env edit.

  4. After QA, open a PR into develop. Merge it only if it goes into the next release — everything that is on develop when the release is made is released.

Releasing a new version

The release manager makes the release in the CI/CD Manager (Finalize Sprint). Nobody runs a release script.

  1. Finalize Sprint. The CI/CD Manager:

    • sets docs/antora.yml to X.Y.Z without prerelease:;

    • writes the version X.Y.Z on develop, in one commit, into every other file that carries it: VERSION, BUILD (reset to 0), docs/modules/ROOT/pages/index.adoc (:revnumber: and the Version line), and the own-image pins in prometheus-grafana-stack/.env.example and docker-compose.yml, so misc/check-versions.sh passes on it;

    • writes the changelog pages for X.Y.Z on develop (see Changelog pages);

    • opens the pull request develop → main.

  2. Review and merge the release PR with “Create a merge commit”. Not squash, not rebase: main and develop must keep a shared history. Merge nothing else into develop while the release PR is open.

  3. After the merge the CI/CD Manager:

    • tags release-X.Y.Z on the merge commit;

    • creates release/X.Y.Z from it;

    • moves develop to X.Y.(Z+1): VERSION, BUILD, index.adoc and docs/antora.yml in one commit, then prerelease: -dev in docs/antora.yml.

  4. The push to main runs the own-image builds (they publish the X.Y.Z image tags) and docs-pdf.yml, which uploads misc/hub/install.sh and the PDFs to the hub.

Before the release PR is merged, work through the Interactive installer gate.

Hotfixes for a released version

  1. Branch hotfix/<DEV-xxxx>-<slug> from release/X.Y.Z.

  2. Fix, test, and merge it into release/X.Y.Z through a PR. Installs pinned with --branch release/X.Y.Z get the fix.

  3. Bring the same fix into develop (cherry-pick, through a PR), so the next release carries it.

The default install is main, and a fix on release/X.Y.Z does not reach main by itself. Customers who install the default get the fix with the next release X.Y.(Z+1) from develop. When a fix is urgent for everybody, release the next version through Releasing a new version instead.

Commits and release notes

  • Conventional Commits are required. .github/workflows/commit-lint.yml checks every PR into main, develop or release/** (misc/lib/conventional-commits.sh; merge, fixup and squash commits are exempt).

  • Put the Jira key in the subject: fix(snmp): DEV-1234 keep the privacy password when SNMPv3 is edited again. The release notes list Jira issues, and QA and customers read issues, not commits.

  • Use only these types: feat, fix, perf, docs, refactor, test, build, ci, chore, refs. Any other type (style, revert, security, …) is left out of the release notes. A security fix is a fix.

  • A breaking change is marked with !: feat(installer)!: …. A BREAKING CHANGE: footer without the ! is not detected.

The release notes are made by the CI/CD Manager from these commits and the Jira tickets they name, for the commits since the previous release tag. The full rules are in the docs portal guide Release Notes Guide (ci-cd module).

There is no CHANGELOG.md entry, no changelog fragment and no GitHub Release for a release made by the CI/CD Manager.

Changelog pages

The CI/CD Manager writes them on develop at Finalize Sprint, so they reach main with the release PR:

File Content

docs/modules/ROOT/pages/changelog/X.Y.Z/external.adoc

For customers: breaking changes, the Jira issues raised for a customer, images and dependencies.

docs/modules/ROOT/pages/changelog/X.Y.Z/qa.adoc

For QA: every Jira issue in the release, and the maintenance work.

docs/modules/ROOT/pages/changelog/X.Y.Z/all.adoc

Every commit, by type.

docs/modules/ROOT/pages/changelog/X.Y.Z/index.adoc

The release page, linking the three.

docs/modules/ROOT/pages/changelog/index.adoc

Every release, newest first.

docs/modules/ROOT/changelog-nav.adoc

The changelog menu, newest first. It is a separate file (the second entry under nav: in docs/antora.yml) because misc/md2adoc.sh rewrites nav.adoc on every docs commit.

Do not edit these pages by hand. To correct a changelog, fix the commit message or the Jira ticket and regenerate the changelog in the CI/CD Manager.

Which ref carries which docs version

The docs portal (docs-portal antora-playbook.yml) publishes grafana from the release/ branches (minus release/.x) plus the legacy bare 1.6.x refs. It does not publish develop, main or tags.

  • Version X.Y.Z is published from release/X.Y.Z and from nothing else. Two refs that claim the same version stop the whole portal build — this is why only the CI/CD Manager creates release/* branches.

  • develop carries the next version with prerelease: -dev and is not published.

Fixing released docs (a postfix)

A documentation mistake found after a release is fixed by a docs-only PR into release/X.Y.Z. The portal republishes it; no new tag is needed. Bring the same commit into develop so the next release does not bring the mistake back.

Our own images

alertmanager-config-ui and snmp-catcher are built by this repository’s CI (.github/workflows/<name>.yml), not mirrored. An installed host runs the tags in prometheus-grafana-stack/.env.example; every stack run brings the host’s .env back to that set (sync_image_versions).

  • A release pins the release. The release commit on develop sets both pins (.env.example and the compose defaults) to X.Y.Z. misc/check-versions.sh refuses a main or release/X.Y.Z whose pins differ from VERSION; on other branches the two places only have to agree with each other.

  • CI publishes the version tag from main and release/ only. Every build is tagged <branch> and <branch>-<sha>; on main and release/ also with VERSION. The workflows build on a change to VERSION or .env.example, so the merge of the release PR builds the X.Y.Z images. Until those builds finish (a few minutes), an install of main cannot pull the new tags. Do not announce the release before both builds are green: gh run list --workflow alertmanager-config-ui.yml --branch main (same for snmp-catcher.yml).

  • Feature, bugfix and develop installs run their own build. On any branch other than main and release/*, the installer (ensure_mirror_pins → follow_branch_image_pins) points each own image at the branch’s tag when the registry has it, and otherwise keeps the pin. An air-gapped host keeps the pin.

Versions: VERSION and BUILD

  • VERSION is the version of the branch. The CI/CD Manager writes it at the release (X.Y.Z) and moves develop to the next version after it. docs/antora.yml, index.adoc and, on main/release/*, the own-image pins must match it — misc/check-versions.sh checks that (the pre-commit hook runs it).

  • BUILD is reset to 0 at every release. misc/version-string.sh prints the runtime string X.Y.Z-b0.0.N that a running deployment reports; it is not a release identity — the release identity is the release-X.Y.Z tag.

Branch protection

  • main and develop — classic branch protection: changes land by PR, with commit-lint required.

  • release/** — the release-branches ruleset: changes land by PR, with commit-lint required.

  • The CI/CD Manager’s GitHub account is on the bypass list of all three. It commits the version and changelog to develop, creates release/X.Y.Z, and moves develop to the next version without a PR. Nobody else pushes to these branches directly.

  • docs-pdf.yml’s workflow_dispatch input bump_type would push a VERSION bump to main as github-actions[bot]; branch protection blocks it, which is correct — the CI/CD Manager is the only thing that moves VERSION.

Interactive installer gate

The install command in our own docs is curl … | sudo bash -s, and the installer draws a menu over it. That path has broken in ways no unit test saw: a black screen on MobaXterm under sudo, and a run that hung forever waiting for a keypress on a /dev/tty nobody was watching.

Before the release PR is merged, work through interactive-release-checklist.md. It has four parts and all four are required:

  1. The automated Phase 1 suite — bash misc/testing/interactive/run-phase1.sh, mirrored in CI by the interactive job in .github/workflows/sandbox-tests.yml. Compare against the absolute counts recorded in the checklist, not only against FAILED STAGES: 0: every gate asserts “at or above a floor”, which a suite that quietly shrank still clears.

  2. Mutation coverage — bash misc/testing/scripts/check-red-verify.sh plus a scoped red-verify.sh sweep. A suite where every test passes proves nothing until the tests have been watched to fail against the defects they guard.

  3. A manual client matrix — OpenSSH, Windows Terminal, MobaXterm and PuTTY / a web console. The automated suites run on terminals we control and cannot speak for the clients customers actually use.

  4. Acknowledgement of the known open defect: Ctrl-C at the config menu does not restore the terminal. Phase 1 reproduces it and neither state fails CI, so a release must not be signed off assuming it is fixed.

Three prerequisites are easy to miss and silent when absent, so section 0 of the checklist covers them: the pre-commit gate does not exist until bash misc/install-hooks.sh --yes has been run (once per clone — in a worktree the hook lives in the shared git directory and covers every worktree); tmux must be installed on every runner carrying the ft-ci label, because the interactive job’s rendering suite refuses to skip under CI; and a Docker daemon must be running on the machine you sign off from, unless promtool is installed locally. Without either, validate-all’s Test 41 fails with failed to connect to the docker API, and step 2’s red-verify.sh sweeps abort with exit 65 and the baseline run is not a usable reference — a red baseline, not a broken gate.

The agent-facing version of the same rule — which test to update when you touch TTY detection, /dev/tty, screen/cursor handling or the sourced UI — is in AGENTS.md at the repository root.

Bootstrap commands

  • …​ | bash -s — <ip> — the latest release, from main (the default of REPO_BRANCH in misc/hub/install.sh, which stays main).

  • …​ | bash -s — --branch <ref> <ip> — a specific branch: a released version (release/X.Y.Z), develop, or a feature branch for QA. It must be a branch, not a tag: bootstrap checks it out as origin/<ref>. misc/testing/scripts/check-doc-commands.sh checks every documented --branch <ref> against origin.

  • …​ | bash -s — --version-list — show all versions.

Before 1.7.1

What the earlier process left in the repository, and what to do with it:

  • Tags. Releases up to 1.6.9 are bare X.Y.Z tags, 1.7.0 is v1.7.0. From 1.7.1 the tag is release-X.Y.Z. The CI/CD Manager finds v1.7.0 as the previous release, so the 1.7.1 release notes start after 1.7.0.

  • release/1.7.0 stays: it publishes the 1.7.0 docs.

  • release/1.7.x (the LTS line misc/release.sh tag created) is not used. The portal does not publish it.

  • Not used for releases any more: misc/release.sh (cut, build, tag), misc/release-notes/generate.sh, release.json, .changelogs/ fragments, CHANGELOG.md, .github/workflows/changelog-check.yml (it only reacts to v* tags) and the LTS logic in branch-sync-gate.yml and misc/check-versions.sh. They are removed in a separate change.

  • The 1.7.0 changelog has only external.adoc (and release.json), made by generate.sh. Releases from 1.7.1 have all three pages.


branch release/1.7.1 · commit e70021b6498caa7f94c439e9d43703e4496f41e2 · page generated October 07, 2026