- add chains.tekton.dev/transparency-upload annotation to publish PipelineRun - propagate IMAGE_URL + IMAGE_DIGEST as pipeline-level results for Chains binding - add IMAGE_URL result to push-image-by-digest task (required for image→provenance bind) - document cosign verify-attestation --type slsaprovenance in README - update ADR (release-pipeline-migration.md) with Chains addendum; remove from out-of-scope Closes forgejo-mcp-46j
13 KiB
ADR: Migrate Release Workflow to op1st Tekton Pipeline
- Status: Accepted (hard cutover complete)
- Date: 2026-05-25 (initial) / 2026-05-26 (hard cutover)
- Tracking: forgejo-mcp-d4b (initial migration, closed) → forgejo-mcp-n85 (soft-disable Forgejo trigger, closed) → forgejo-mcp-gdz (hard cutover, closed) → forgejo-mcp-td8 (canonical CI gate decision, closed)
Status history
| Date | Status | Change |
|---|---|---|
| 2026-05-25 | Proposed | PR #150 merged: Tekton release pipeline + Tasks + ADR + runbook + README verify chapter |
| 2026-05-25 | Accepted (soft-cutover) | .forgejo/workflows/release.yml trigger changed from push: tags: v* to workflow_dispatch only (bead forgejo-mcp-n85). Tekton is now the sole auto-firing release path; Forgejo workflow kept as manual fallback. |
| 2026-05-26 | Accepted (hard cutover) | .forgejo/workflows/release.yml and ci.yml deleted (commit 1777cca). Tekton is now the sole release and CI path. See addendum below. |
Context
The forgejo-mcp release is driven by .forgejo/workflows/release.yml, a
Forgejo Actions workflow that runs on Codeberg's hosted runner whenever a
v* tag is pushed. It performs six jobs in a single linear job:
- Checkout with full history (for changelog).
- Install Go via
actions/setup-go. - Install
syft(CycloneDX SBOM generator) andcosign(artifact signer). - Run
goreleaser releaseagainst the tag, producing platform archives, per-archive SBOMs, and achecksums.txt. - Smoke-test the freshly-built
linux/amd64binary (forgejo-mcp versionmust contain the tag). - Cosign-sign
checksums.txt(skipped ifCOSIGN_PRIVATE_KEYunset) and upload the signature to the Codeberg release as an asset. - Build a
.mcpb(Claude Desktop Extension) per(goos, goarch)pair (linux/darwin × amd64/arm64), version-stamp the bundledmanifest.json, and upload each to the Codeberg release.
Parallel to this, the project already uses op1st Tekton (OpenShift
Pipelines + Pipelines-as-Code) for PR and push-to-main CI, registered in
namespace op1st-pipelines via the Repository CR
.tekton/repository.yaml. The open bead forgejo-mcp-td8 captures the
follow-up decision of which CI gate (Forgejo Actions vs Tekton) becomes
canonical for PRs; release follows the same direction once that lands.
Decision
Add a Tekton release PipelineRun (.tekton/on-tag-push-release.yaml)
that mirrors the seven Forgejo Actions steps as three reusable Tasks under
.tekton/tasks/:
| Task | Purpose |
|---|---|
goreleaser-release.yaml |
install syft + goreleaser, run release, smoke-test |
cosign-sign-release.yaml |
sign-blob checksums.txt, upload .sig to release |
mcpb-pack.yaml |
pack .mcpb per platform, upload to release |
PipelinesAsCode trigger is a tag push:
pipelinesascode.tekton.dev/on-event: "[push]"
pipelinesascode.tekton.dev/on-target-branch: "[refs/tags/v*]"
The existing .forgejo/workflows/release.yml stays untouched. The two
pipelines run in parallel for at least two releases (~2–4 weeks), after
which a follow-up decision (see "Cutover criteria" below) selects the
canonical one.
Why not …
-
Hard cutover in this PR. First-time pipeline runs surface secret wiring bugs, image incompatibilities, and PaC parameter quirks. Losing a release to those is expensive — the package is consumed by Claude Desktop users who pin to specific
.mcpbbuilds. Parallel-run buys cheap insurance: the Forgejo workflow still ships the release even if Tekton fails. -
Cross-repo
workflow_callfrom Forgejo Actions into Tekton. Considered but rejected: doubles the moving parts (Forgejo orchestrator- Tekton executor), depends on Forgejo Actions remaining the entry
point indefinitely, and the C5 spike (
forgejo-mcp-33k) for cross-repo workflow_call on Codeberg's Forgejo v11.x is unverified.
- Tekton executor), depends on Forgejo Actions remaining the entry
point indefinitely, and the C5 spike (
-
Tekton Chains for artifact signing instead of explicit
cosign sign-blob. Tekton Chains is deployed (seemanifests/applications/op1st-pipelines/tekton-chains.yamlinop1st-emea-b4mad) and signsTaskRunprovenance automatically. But Chains signs the run record, not the GoReleaser artifact files uploaded to the Codeberg release. Users verifying a downloaded archive expect a.sigfile matching the existing Forgejo workflow's contract; preserving that contract requires explicitsign-blob. Chains-emitted provenance is a complementary signal we can publish later — not a replacement. -
Single mega-image with all tooling. Slower image rebuilds, no cache reuse, and forces the maintainer to own a private registry path. Per-Task images (
golang:1.25,cosign/cosign:v2.4.1,node:22-bookworm) are upstream-maintained and pinned.
Secrets
Required in the op1st-pipelines namespace. Both already exist
(provisioned upstream — see "Source of truth" below):
| Secret | Type | Keys | Used by |
|---|---|---|---|
op1st-release-token |
Opaque | token, username |
GoReleaser publish + Codeberg release asset uploads |
cosign-signing-key |
Opaque | cosign.key, cosign.password, cosign.pub |
sign-blob of checksums.txt |
Source of truth
Neither Secret is created directly in op1st-pipelines:
op1st-release-tokenis auto-mirrored fromop1st-gitops/op1st-release-tokenvia the emberstack reflector. Rotation happens once inop1st-gitops; the reflector propagates to every consuming namespace within ~seconds.cosign-signing-keyis provisioned alongside Tekton Chains tooling (matches thesigning-secretshape used by Chains, withcosign.key,cosign.password,cosign.pubkeys).
The pipeline references cosign-signing-key with optional: true — if
absent, the signing step warns and exits 0, matching
.forgejo/workflows/release.yml's fail-open behavior.
Operator-confirmable preconditions
These cannot be validated from inside the cluster (token scopes are not
visible from oc get secret). Operator must confirm once on Codeberg UI:
- The PAT stored in
op1st-release-token.tokencarrieswrite:repositoryongoern/forgejo-mcp(covers release publish + asset upload). Read-only PaC tokens likecodeberg-runner-openshift-pacdo not suffice. - The PAT identity (e.g.
b4mad-release-bot) is whoever should appear as release author in the Codeberg UI. cosign-signing-key.cosign.pubinop1st-pipelinesmatches the normative public key atop1st-emea-b4mad/manifests/applications/op1st-pipelines-tokens/cosign-signing-key.pub. Mismatch meanscosign verify-blobfailures for every downstream user following the README "Verifying Releases" chapter.
Trigger semantics
PipelinesAsCode resolves the matched ref into {{ target_branch }}. For
a push of refs/tags/v2.24.0 the value passed to the PipelineRun is the
full ref. The resolve-tag inline Task strips the refs/tags/ prefix to
produce a bare tag name (v2.24.0) that downstream Tasks use to:
- locate the just-published release via the Codeberg API
(
GET /repos/{owner}/{repo}/releases/tags/{tag}), and - derive
VERSION="${TAG#v}"for filename templating consistent with.goreleaser.yml'sname_template.
Cutover criteria
Promote Tekton to canonical (and remove .forgejo/workflows/release.yml)
when all of the following hold across two consecutive releases:
- Tekton release pipeline succeeds end-to-end without manual intervention.
- All eight expected assets (4× archive, 4×
.mcpb,checksums.txt, optionalchecksums.txt.sig, SBOMs) are present on the Codeberg release and match the Forgejo-produced equivalents byte-for-byte (where deterministic) or in structure (SBOMs). - Wall-clock pipeline duration is within 1.5× of the Forgejo workflow, measured from tag push to release-complete.
- Cosign signature verification succeeds against the published
cosign.pubfor both pipelines' outputs.
Failure modes that defer cutover: PaC tag-push trigger does not fire on
Codeberg's Forgejo v11.x (then we keep .forgejo/workflows/release.yml
and revisit when Forgejo lands the relevant fixes); image pulls blocked
from op1st-pipelines (then we mirror to an internal registry first).
Rollback
Delete .tekton/on-tag-push-release.yaml. The three Task files are
inert without a PipelineRun referencing them; leaving them in tree
costs nothing. The Forgejo workflow remains the authoritative release
path until the file is removed in a follow-up commit.
Addendum: Hard cutover — 2026-05-26
Decision
Hard cutover executed after one fully successful Tekton release (v2.25.1) rather than the two specified in the original cutover criteria.
.forgejo/workflows/release.yml and .forgejo/workflows/ci.yml deleted in
commit 1777cca. Bead forgejo-mcp-gdz closed.
Why one run instead of two
The two-run bar existed to catch infrastructure surprises: secret wiring bugs, image incompatibilities, PaC parameter quirks. By 2026-05-26 all of those were resolved:
| Run | Outcome | Blocker resolved by |
|---|---|---|
| v2.24.0 | goreleaser exit 127 | release-tools image (forgejo-mcp-1b4) |
| v2.24.1 | goreleaser dirty-tree | GOCACHE/GOMODCACHE outside workspace |
| v2.24.2 | cosign step failed (distroless, no shell) | release-tools image (forgejo-mcp-1b4) |
| v2.25.0 | cosign step failed (--output-signature deprecated) |
fix in commit cee6465 |
| v2.25.1 | all tasks succeeded, .sig present |
— first clean end-to-end run |
After v2.25.1 the pipeline was demonstrably stable. No infrastructure surprises remained. Running a second release solely to satisfy the original count added delay without adding safety.
Additionally, the security posture improved with this cutover:
- cosign signing key split into
cosign-signing-key-artifacts(release artifacts) andcosign-signing-key-images(release-tools image), so a compromised release-tools image can no longer forge artifact signatures (PR #164, bead forgejo-mcp-j52).
Updated secrets table
| Secret | Keys | Used by |
|---|---|---|
op1st-release-token |
token, username |
goreleaser publish + Codeberg release asset uploads |
cosign-signing-key-artifacts |
cosign.key, cosign.password, cosign.pub |
sign-blob of checksums.txt in release pipeline |
cosign-signing-key-images |
cosign.key, cosign.password, cosign.pub |
signing the release-tools container image |
The original cosign-signing-key Secret remains in op1st-pipelines as a
legacy artefact; it is no longer referenced by any Task.
Rollback
The Forgejo Actions workflows are deleted. Rollback requires restoring them
from git history (git show HEAD~1:.forgejo/workflows/release.yml) and
re-adding the tag trigger. The Tekton pipeline remains in tree and can be
disabled by removing .tekton/on-tag-push-release.yaml.
Out of scope
- Migrating CI (PR + push-to-main) Forgejo Actions to Tekton — tracked
in
forgejo-mcp-td8. This ADR covers release only. - Konflux integration for container builds — currently no container release exists; revisit when one does.
Addendum: Tekton Chains SLSA provenance — 2026-05-26
Decision
Enable Tekton Chains SLSA v1.0 provenance attestation for the release-tools
publish PipelineRun (forgejo-mcp-46j).
Changes:
release-tools-on-tag-publish.yaml— addedchains.tekton.dev/transparency-upload: "true"annotation; addedIMAGE_URLandIMAGE_DIGESTas pipeline-level results propagated frompush-by-digest. Chains reads these results to bind image digest to provenance.push-image-by-digest.yaml— addedIMAGE_URLresult (plain registry URL, no tag/digest) required for Chains image-binding.README.md— new "Verify SLSA provenance" section under "Verifying Releases".
Why
A cosign signature alone attests signer-holds-key. Tekton Chains in-toto SLSA provenance additionally binds image digest → PipelineRun → git commit → builder identity. This closes the provenance gap identified in the adversarial review of the release-tools image (L5 finding).
chains.tekton.dev/transparency-upload: "true" causes Chains to upload the
attestation envelope to Rekor, making it publicly auditable.
Verify
cosign verify-attestation \
--type slsaprovenance \
--key cosign-images.pub \
"codeberg.org/operate-first/release-tools:${TAG}"
See README "Verifying Releases → Verify SLSA provenance" for the full procedure including key fetch.