Skip to content

DEVEX-1650: add _compute-rt-tag composite action for Release Train v2#132

Closed
pdodgen-revparts wants to merge 1 commit into
mainfrom
DEVEX-1650-compute-rt-tag
Closed

DEVEX-1650: add _compute-rt-tag composite action for Release Train v2#132
pdodgen-revparts wants to merge 1 commit into
mainfrom
DEVEX-1650-compute-rt-tag

Conversation

@pdodgen-revparts

@pdodgen-revparts pdodgen-revparts commented Jun 16, 2026

Copy link
Copy Markdown
Contributor

Summary

Adds _compute-rt-tag composite action — the foundational piece for Release Train v2 (DEVEX-1579) per-repo Build.yaml rt-* path. Tracked by DEVEX-1650 (Phase 0 spike).

Behaviour

On push to rt-<N>, callers of this action get:

  • Next rt-N tag computed from git context (no state ledger).
  • Lightweight tag pushed + matching GH Release created with prerelease=true — atomically as a pair.

Base version comes from git tag --points-at $(git merge-base origin/main HEAD). Iter is the count of existing ${base}-rc.${N}.* tags + 1.

⚠️ Tag format change from DEVEX-1579 spec

Spike found that composer's VersionParser rejects the originally-proposed vX.Y.Z-rt.<N>.rc.<iter> format. Only standard pre-release identifiers (rc, beta, alpha, dev, patch) are accepted.

New format: vX.Y.Z-rc.<N>.<iter> (e.g., v4.7.0-rc.147.3).

  • Composer-accepted with minimum-stability: stable.
  • Sorts correctly via standard semver comparison.
  • Train + iter extractable via rc\.(\d+)\.(\d+).
  • 3-segment shape distinguishes new v2 tags from legacy 2-segment v1 vX.Y.Z-rc.<iter> tags.

Confirmed with the assignee; spec amendment will follow on the parent ticket.

Tests run locally

  • Synthetic git state with v4.6.0, v4.7.0 on main, rt-147 with rc.147.1, rc.147.2 already → action emits v4.7.0-rc.147.3. ✅
  • Main moves forward to v4.7.1 after rt-147 cut → base STILL v4.7.0 (frozen at cut SHA). ✅
  • Missing base tag at cut SHA → action fails with clear error. ✅
  • Pre-release tag (e.g. v4.7.0-alpha.1) at cut SHA → regex correctly rejects; treated as missing base. ✅
  • Legacy 2-segment tags don't pollute iter count (glob rc.147.* doesn't match rc.7). ✅

Caller requirements

Documented in the action's README. Key points:

  • actions/checkout with fetch-depth: 0 and fetch-tags: true.
  • Build.yaml declares concurrency: { group: 'rt-build-${{ github.ref }}', cancel-in-progress: false } to prevent iter races.
  • Token with contents: write.

Not in this PR

  • No app-side wiring yet (Phase 1 / Phase 2 / Phase 3 work).
  • No common-side rt-build.yaml yet (Phase 1).
  • No start-release-train.yaml or pin-common-on-rt.yaml yet (Phase 1).

Reviewing the action's logic + tag format change is the ask.

Composite action for the per-repo Build.yaml rt-* push trigger. Computes
the next pre-release tag for an rt-N branch and emits both the git tag
and a matching GitHub Release (prerelease=true) atomically.

Tag format is vX.Y.Z-rc.<N>.<iter> (e.g. v4.7.0-rc.147.3) rather than the
originally-proposed vX.Y.Z-rt.<N>.rc.<iter> — composer's VersionParser
rejects custom pre-release identifiers like 'rt', accepting only the
standard set (rc, beta, alpha, dev, patch). The 3-segment shape
distinguishes new v2 tags from legacy 2-segment v1 'vX.Y.Z-rc.<iter>'
tags and remains uniquely parseable via 'rc\.(\d+)\.(\d+)'.

Base version is derived from the tag at the cut SHA (git merge-base
between origin/main and HEAD), relying on main tagging every push.
Iter is the count of existing rc.<N>.* tags plus one. Build.yaml must
declare a concurrency group on rt-* to prevent iter races; this action
does not serialise on its own.

Verified against a synthetic git state: correct tag computation,
base-version stability when main moves forward, error handling for
missing-base, pre-release-rejection (alpha/beta) for base extraction,
and no glob collision with legacy 2-segment rc tags.
@cursor

cursor Bot commented Jun 16, 2026

Copy link
Copy Markdown

PR Summary

Medium Risk
The action pushes tags and creates releases with contents: write; incorrect concurrency or missing main tags could cause duplicate-tag failures or wrong versioning, but scope is isolated to new CI plumbing with no production app changes yet.

Overview
Introduces a new composite GitHub Action _compute-rt-tag for Release Train v2: on rt-<N> pushes it derives the frozen base vX.Y.Z from the merge-base with main, bumps the rc iteration from existing tags, pushes a lightweight tag, and creates a matching prerelease GitHub Release in one flow (no state ledger).

Tag scheme deviates from the original DEVEX-1579 spec: uses vX.Y.Z-rc.<N>.<iter> (e.g. v4.7.0-rc.147.3) instead of vX.Y.Z-rt.<N>.rc.<iter> so Composer’s VersionParser accepts the prerelease id; README documents caller requirements (fetch-depth: 0, rt-build concurrency group, contents: write) and failure modes.

No app Build.yaml or other workflow wiring in this PR—action + documentation only.

Reviewed by Cursor Bugbot for commit 364fb04. Bugbot is set up for automated code reviews on this repo. Configure here.

@cursor cursor Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cursor Bugbot has reviewed your changes and found 2 potential issues.

Fix All in Cursor

❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.

Reviewed by Cursor Bugbot for commit 364fb04. Configure here.

gh release create "${{ steps.compute.outputs.tag }}" \
--prerelease \
--title "${{ steps.compute.outputs.tag }}" \
--notes "Release Train ${{ steps.compute.outputs.train_number }}, rc.${{ steps.compute.outputs.iter }} (base ${{ steps.compute.outputs.base_version }})"

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Tag without release on failure

Medium Severity

Tag push and GitHub prerelease creation run as separate steps. If git push succeeds and gh release create fails (or the job is cancelled), the remote tag remains while no matching release exists. A later run counts that tag and bumps iter, leaving a tag with no release and breaking the documented guarantee that tag and prerelease always exist as a pair.

Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit 364fb04. Configure here.


iter_count=$(git tag -l "${base}-rc.${train_n}.*" | wc -l | tr -d ' ')
iter=$((iter_count + 1))
tag="${base}-rc.${train_n}.${iter}"

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Base version shifts after main merge

Medium Severity

base_version and the rc tag prefix are derived from git merge-base origin/main HEAD on every run. After origin/main is merged into the rt-N branch, that merge-base moves to the updated main line, so the base tag and ${base}-rc.${train_n}.* iter counting can change mid-train instead of staying fixed at the original cut.

Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit 364fb04. Configure here.

@pdodgen-revparts

Copy link
Copy Markdown
Contributor Author

Superseded by encodium/release-train#1 — RT v2 machinery moved to a private repo per DEVEX-1650 follow-up. Keeping the action in the public .github repo would have exposed the orchestration surface and required leaning on a settings-only RT_PARTICIPANTS org variable. Private repo gives us proper PR review on the participant list, isolation of RT v2 internals, and a natural home for future RT machinery (workflows in Phase 1; captain dashboards / scripts later).

@pdodgen-revparts pdodgen-revparts deleted the DEVEX-1650-compute-rt-tag branch June 16, 2026 21:48
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