Architecture Of Konflux

54. Start versioning Tekton Tasks responsibly

Date: 2025-10-06

# Status

Accepted

Relates to ADR 53. Trusted Tasks model after build-definitions decentralization.

# Context

The upstream Tasks that Konflux releases today typically do not follow a sensible versioning scheme. They do have version numbers, but the numbers serve only two purposes:

For other kinds of changes, whether they be new features, bug fixes or periodic dependency updates, Task maintainers leave version numbers unchanged.

This has consequences.

# Users get opaque updates

Most of the time, when a Konflux user receives a PR that updates their Tasks, they see something like this:

Package Change Notes
quay.io/konflux-ci/tekton-catalog/task-apply-tags e0de426 -> f44be1b
quay.io/konflux-ci/tekton-catalog/task-build-image-index 3bf6e4e -> 79784d5
quay.io/konflux-ci/tekton-catalog/task-buildah-remote-oci-ta 0.4 -> 0.5 :warning:migration:warning:
quay.io/konflux-ci/tekton-catalog/task-clair-scan 0.2 -> 0.3 :warning:migration:warning:

For the tasks with breaking changes, they get some (usually insufficient) information about what changed.

For those without breaking changes, they get nothing. What changed between e0de426 and f44be1b? Not even the maintainers know.

It would not be at all unreasonable for a user to refuse to merge these kinds of PRs (except that Conforma would eventually flag their tasks as outdated.)

# Maintainers cannot easily communicate critical fixes

When a Task maintainer discovers and fixes a critical bug, they typically wish to announce this publicly, e.g. on the Konflux mailing list.

With the current versioning practices, how does the maintainer go about this?

We’ve discovered a bug in task-foo version 0.2. Please upgrade to version 0.2.

Hmm, no, that’s not right.

We’ve discovered a bug in task-foo:0.2@sha256:baadbaad. Please upgrade to sha256:deadbeef

Better. But a user has 0.2@sha256:baddecaf. Are they affected? Who knows.

We’ve discovered a bug in task-foo version 0.2, revisions released between 2025-08-01 and 2025-10-01. Please upgrade to a sufficiently new revision.

Okay, at least it’s an interval now. But are we really going to ask the user to run skopeo inspect --no-tags --format '{{.Created}}' docker://${my_task_ref} to figure out if they’re affected?

We’ve discovered a bug in task-foo versions >=0.2.2,<0.2.5. Please upgrade to version 0.2.5.

(Is what the maintainer wishes to write, cursing the lack of versioning.)

# Policy authors cannot set requirements for Task versions

The authors of Conforma policies may want to have the ability to require specific versions of certain Tasks. Today, this desire is expressed in the form of the expiry mechanism for Trusted Tasks:

This mechanism is rather clunky and not very flexible. And, for reasons explained in ADR 53, the mechanism will not work after the build-definitions decentralization.

A more flexible solution would allow the policy author to allow/disallow specific version ranges. Such a solution cannot work if dozens of different versions of the same Task share the same version number.

# Decision

# Mark each meaningful change with a version update

For Tasks at version 0.x (in initial development), you may continue to mark breaking changes with minor version updates and start marking non-breaking changes with patch version updates. Make sure to move to version 1.0 when the Task matures.

For Tasks at version 1.0 or greater, follow Semantic Versioning.

When releasing Tasks as OCI bundles:

ℹ️

The current build pipeline for Task bundles applies the version annotation automatically. The source for the version value is the app.kubernetes.io/version label from the Task YAML definition.

Task maintainers can configure their ReleasePlanAdmissions to automatically use the version annotation as a tag for the released bundle. The repository that holds Red Hat release configurations has a CI check which ensures the relevant RPAs are configured correctly.

This ADR leaves some freedom to each Task maintainer team to decide how to ensure changes get versioned. The suggested approach would be to version manually and set up CI in a way that encourages/enforces versioning. See Appendix 1: The technicals of Task versioning.

# Don’t require Task repositories to follow Tekton Catalog layout

The Tekton Catalog layout, which places each version of a Task in a separate task/${task_name}/${version}/ directory, causes far too much overhead when updating Task versions. If we’re going to require version updates for each meaningful change, we cannot ask Task maintainers to follow this layout. See Appendix 1: The technicals of Task versioning for more details.

# Keep a changelog

Version numbers on their own are nice, but without an easy way to see what changed in each version, they’re not that useful.

Document the meaningful changes in each version in a CHANGELOG.md file. The changelog for a specific Task will be at /task/${task_name}/CHANGELOG.md in the repository. See Appendix 3: CHANGELOG.md format for details about the file format.

# MintMaker: don’t assume each x.y version has a MIGRATION.md

Today, MintMaker links MIGRATION.md documents in the PRs it sends to Konflux users (see the example above). This feature assumes that each major.minor version comes with a MIGRATION.md document, that this document is at /task/${task_name}/${version}/MIGRATION.md in the source repository and that the source repository is https://github.com/konflux-ci/build-definitions. The feature will not work after decentralization.

MintMaker will stop trying to link MIGRATION.md files and will instead link the changelog. In case of breaking changes, the changelog should explain how the user can migrate.

# Extend trusted_task_rules mechanism with version-based rules

Extend the mechanism described in ADR 53. Trusted Tasks model after build-definitions decentralization. Give policy authors the ability to constrain the acceptable Task versions.

Make it possible to:

Like most Conforma rules, the Task version rules should support the effective_on concept. A policy author typically will not want to disallow a version range immediately, but will want to give users some lead time.

The Appendix of ADR 53 illustrates how the Task version rules could look.

# Consequences

# Positive

Users get transparent update PRs.

Maintainers can easily communicate critical fixes.

Policy authors can set requirements for Task versions. This can replace the Task expiry mechanism lost during decentralization.

# Negative

The Tekton Catalog layout gives the git resolver a way to refer to a specific version of a Task by filepath. Without the catalog layout, users will not be able to specify exact Task versions when using the git resolver (except by commit revision).

ℹ️ As explained in ADR 53, git references for decentralized Tasks will not initially be trusted, so this is not a big loss. The ADR mentions possible approaches to re-enable trust in git references, one of which could offer a straightforward solution: Development happens in decentralized repos, which don’t have to follow the catalog layout. A release pipeline pushes the tasks into a centralized repo that does follow the catalog layout.

Requiring a version update for each code change will cause some overhead for contributors and/or maintainers. You may be thinking the versioning should be automatic. Appendix 2: In defense of manual versioning argues that automatic versioning is not a good idea.

# Appendix

# The technicals of Task versioning

# Repository layout

The current versioning practices for Konflux Tasks require a repository layout with separate directories for each major.minor version:

task
└── hello
    ├── 0.1
    │   └── hello.yaml
    ├── 0.2
    │   └── hello.yaml
    ├── 1.0
    │   └── hello.yaml
    └── 1.1
        └── hello.yaml

Updating a Task’s version involves copy-pasting the Task definition and related resources into a new directory. This brings problems:

The suggested repository layout going forward would be:

task
└── hello
    └── hello.yaml    (version defined only by the app.kubernetes.io/version label)

In case you want to maintain more than one “version stream” at a time:

task
└── hello
    ├── 1.x
    │   └── hello.yaml  <- branched off into separate dir when moving to 2.0
    └── hello.yaml      <- latest 2.x

# CI setup

We want to make sure each meaningful change to a Task definition results in a version update. We may want to leave maintainers/contributors some freedom to skip version updates for changes they don’t consider meaningful (e.g. formatting, description texts).

A flexible way to achieve that would be to release Tasks only when their version number changes (don’t even build the Task bundle otherwise).

The CI should prompt the contributor to consider updating the version and the CHANGELOG. It should explain that the Task will not get released unless the version changes.

💡 AI code review might work well here. You would want to teach the reviewer to decide whether the change is meaningful and post a comment accordingly.

# Dealing with MintMaker updates

Automated PRs for dependency updates may become a problem. They’re already seen as a chore today by many Task maintainers. If manual action is required before a MintMaker PR can merge, the most likely outcome is that MintMaker PRs will stop getting merged.

There’s a few options to deal with this:

Some combination of the above should be enough to make automated dependency updates tolerable even with the versioning requirements.

# In defense of manual versioning

Having to manually update the Task version in the source code may feel like an avoidable chore. Surely we could automate it, right?

If the Pull Request doesn’t update the version, just auto-increment the patch number.

This would allow the version-based Task expiry mechanism to somewhat function. But would fail spectacularly at most other aspects of software versioning. How do you maintain a changelog for an auto-incrementing version? How do you make sure contributors differentiate between bug fixes and new features?

Okay, let’s derive both the version and the changelog from semantic commits.

Hey, that’s not a bad idea. It’s a pretty common approach with a lot of existing automation, such as semantic-release. It’s unlikely any of it would work out of the box for Konflux Tasks, but surely it wouldn’t be hard to build our own.

Let’s say you’ve built this solution. You’ve taught your contributors to follow semantic commit conventions responsibly. You’ve taught your team to review commit messages as carefully as they review the code. You have a solution that auto-generates a changelog. You probably employed an LLM to extract the user-facing details out of the maintainer-facing commits messages, and to fill in the gaps for those commit messages that don’t come with any useful information. It’s beautiful. Just one question left to answer. When do you run this automation?

Konflux releases are, by default, automatic - each merge triggers a release. For a repository where you maintain dozens of Tasks, you probably want to keep it that way. If you’ve just spent two weeks building an auto-version + auto-changelog solution, you definitely don’t want releasing to be a manual affair. Since there’s no “release preparation” step, that leaves you one option - run the automation at release time (or in the merge pipeline that triggers a release immediately upon completion, which is effectively the same). So the tool has determined the next version, the release pipeline applied that version and released the Task. The tool also returned the changelog content. What do you do with it?

Taking inspiration from semantic-release, just dump the changelog into a GitHub release? Take a look at semantic-release/releases and consider if that’s the style of changelog you would want to see. And now remember that this is a changelog for a single cohesive item - there’s only one thing to version. Your repository is a collection of dozens of Tasks, each versioned separately. No, each Task is going to need its own CHANGELOG.md. But the changes are already merged, the release is already happening or about to happen. It’s too late to update files in the source repo. No matter, let’s send a PR. Maybe the changelog update will lag behind the actual release, but that’s not a big deal.

So, for every merge, you get an automated PR to update the CHANGELOG.md. You get the uneasy feeling that the time savings you get from the automation are in the negative numbers. If only there was a way to update the changelog in the same PR that introduces the change.

And then one day, the automation mis-versions a breaking release because someone made a typo in the commit message, and you scramble to revert the changes and re-release them properly. If only there was a way to vet the version number before the release goes live.

The solution to both problems turns out to be simple. Define version numbers statically in the source code. Ask contributors to update both the version numbers and the changelog documents manually.

To salvage the failed automation, you at least re-purpose it as a GitHub workflow that a contributor can optionally invoke by commenting on the Pull Request.

ℹ️ In all seriousness, this style of automation could genuinely be helpful, especially for dealing with MintMaker updates. Though something far simpler would likely suffice.

To summarize: If you want to go the automation route, feel free to do so, as long as it doesn’t go against the spirit of the decisions in this ADR. But consider instead keeping it simple.

# CHANGELOG.md format

The CHANGELOG.md must:

The CHANGELOG.md should:

Example:

# Changelog

## Unreleased

### Changed

- Something so small that it wasn't worth it to do a release for it. It
  will be released the next time someone updates the version.

## 1.0.0

*With this release, do-something-cool reaches a stable 1.x version!
From now on, breaking changes will result in major version bumps.*

### Removed

- The `coolness_level` parameter. The task now always operates at maximum coolness.
  - This is not a breaking change, since Tekton will just ignore the parameter.
    Do consider removing it from your pipelines though.
- **Breaking**: The `coolness_level` result. If you were previously using this
  result in your pipelines, please remove the usage and assume maximum coolness.

## 0.2.0

### Changed

- **Breaking**: Renamed the `coolnessLevel` result to `coolness_level` to align
  on `snake_case` result naming. Please update the usage of this result in your
  pipelines accordingly.

## 0.1.1

### Fixed

- In some cases, the task could do something uncool.

## 0.1.0

### Added

- The initial version of the do-something-cool task!