Skip to content

ci: add release-version validation and PR-title enforcement - #5

Merged
aesslinger merged 1 commit into
mainfrom
ci/semver-checks
Aug 11, 2026
Merged

ci: add release-version validation and PR-title enforcement#5
aesslinger merged 1 commit into
mainfrom
ci/semver-checks

Conversation

@aesslinger

@aesslinger aesslinger commented Aug 11, 2026

Copy link
Copy Markdown
Collaborator

Summary

Adds two CI checks:

1. Release-version validation (release.yml)

A validate job now gates the build matrix on the pushed tag matching .tabularium's version field (stripped of the v prefix):

TAG_VERSION="${GITHUB_REF_NAME#v}"
TABULARIUM_VERSION=$(jq -r .version .tabularium)
[ "$TAG_VERSION" != "$TABULARIUM_VERSION" ] && exit 1

Ported from the tabularis-elasticsearch-plugin sibling — the only Tabularis plugin repo with this check today. This isn't just a nice-to-have: the registry's own manifest schema (registry.tabularis.dev/manifest.schema.json?kind=driver) documents this as a hard rule on the version field — "REQUIRED — must match the release tag stripped of any 'v' prefix. The registry rejects ingests whose tag and manifest version disagree." This catches that mismatch at tag-push time instead of at registry-submission time.

2. PR title convention (ci.yml)

A pr-title job enforces Conventional Commits-style PR titles via amannn/action-semantic-pull-request. Checked what precedent exists first:

  • No sibling Tabularis plugin repo has anything like this.
  • TabularisDB/tabularis (the main app) doesn't either — tags are pushed manually, no PR-title convention.
  • A separate internal repo has a fuller pattern: PR-title-driven auto-semver-bump + auto-tag + auto-release on merge. Decided against porting that whole pipeline here — it's a much bigger behavioral change (replaces manual tagging entirely) with no Tabularis-org precedent, whereas just enforcing the title convention is a small, well-understood addition on its own.

Configured on plain pull_request (not pull_request_target): the latter exists specifically to support PRs from forks with elevated permissions, which this repo doesn't need (all contributors so far have write access) — plain pull_request avoids that elevated-permission surface entirely.

Rebased onto current main, which has moved on since this PR was opened (now at 1.0.0-beta.2, with its own CI/release fixes already merged). This PR no longer carries any version-bump or CHANGELOG changes of its own — those are covered independently on main.

Test plan

  • cargo build/test --lib --bins (82/82)/clippy/fmt --check all pass
  • Both workflow YAML files parse (js-yaml)
  • Version-match check logic manually simulated against the current 1.0.0-beta.2 value (match — passes) and reasoned through the mismatch case (fails, as intended)
  • Confirmed v prefix matches convention used by tabularis and every tagged sibling plugin repo
  • git diff --exit-code Cargo.lock clean (no incidental lockfile drift from the rebase)

- release.yml: a validate job gates the build matrix on the pushed tag
  matching .tabularium's version field (stripped of the v prefix) —
  ported from the tabularis-elasticsearch-plugin sibling's pattern. Not
  just convention: the registry's own manifest schema documents this as
  a hard rule ("the registry rejects ingests whose tag and manifest
  version disagree"), so this catches the mismatch at tag-push time
  instead of at registry-submission time.
- ci.yml: a pr-title job enforces Conventional Commits PR titles via
  amannn/action-semantic-pull-request, on plain pull_request (not
  pull_request_target) since this repo doesn't need fork-PR support and
  the plain event avoids the elevated-permission surface entirely.

Rebased onto the current main (already at 1.0.0-beta.2 with its own
release/CI fixes) — drops the version-bump/CHANGELOG changes this PR
originally carried, since main now covers that independently.
@aesslinger aesslinger changed the title Add release-version and PR-title CI checks; reset version scheme to 1.0.0-beta.1 Add release-version validation and PR-title CI enforcement Aug 11, 2026
@aesslinger aesslinger changed the title Add release-version validation and PR-title CI enforcement ci: add release-version validation and PR-title enforcement Aug 11, 2026
@aesslinger
aesslinger merged commit fce7d21 into main Aug 11, 2026
11 of 12 checks passed
@aesslinger
aesslinger deleted the ci/semver-checks branch August 11, 2026 17:37
aesslinger added a commit that referenced this pull request Aug 11, 2026
A precursor to eventually automating tag/release creation: when a PR's
title implies a version bump, CI now posts a comment suggesting the next
tag/version — without creating anything. Several real design decisions,
each resolved with the user rather than assumed:

- Type -> bump mapping: standard Conventional Commits (feat->minor,
  fix/refactor/perf->patch, docs/style/chore/test/ci/build->none,
  !/BREAKING CHANGE->major).
- Beta-phase behavior: while the baseline version carries a -beta.N/-rc.N
  suffix, a release-worthy PR just increments that stage's counter, not a
  full patch/minor/major recompute -- nothing has shipped stable yet.
- Prerelease channel: NOT signaled in the title. Checked precedent first
  (semantic-release/release-please control channel via labels or release-
  train branches, not embedded title syntax; no Tabularis repo does this
  either) and settled on a required prerelease:alpha|beta|rc|stable PR
  label. Missing label is a hard error -- the job fails rather than
  guessing a default channel.
- Version baseline: latest git tag on main, falling back to main's
  .tabularium version field if no tag exists (true right now).
- "Meaningful change" trigger: only re-comments when the PR's *derived
  classification* (type+breaking+channel) changes, not on every title
  edit -- tracked via a hidden HTML marker in the comment body, robust
  across opened/edited/reopened/synchronize/labeled/unlabeled.
- Marking old suggestions outdated: GitHub's real minimizeComment GraphQL
  mutation with classifier: OUTDATED -- the same action available via the
  web UI's "..." menu -> "Hide comment" -> "Outdated".
- Found and fixed a real trigger gap: the existing pull_request: block had
  no types:, defaulting to opened/synchronize/reopened -- a title-only
  edit never even re-ran CI. Widened to include edited/labeled/unlabeled
  (shared by all pull_request-triggered jobs, including the existing
  pr-title job, which also now re-validates on edited).

Checked precedent before implementing: no sibling Tabularis plugin repo
or tabularis itself has anything like this. A separate internal repo has
a fuller PR-title-driven auto-tag/auto-release pipeline; decided against
porting that whole pipeline here since it's a much larger behavioral
change (replaces manual tagging entirely) with no Tabularis-org
precedent -- this stays comment-only.

Verified thoroughly before trusting the logic in CI:
- Extracted the embedded classification regex and ran it in a real bash
  subshell (not zsh, which handles BASH_REMATCH differently) against 8
  title cases -- all classified correctly, including a deliberately
  unparseable title correctly erroring.
- Extracted the embedded version-arithmetic JS and unit-tested it
  standalone against 7 cases (beta-counter increment, stage transitions,
  graduation to stable from both beta and rc, stable->prerelease
  restart) -- all passed.
- Wrote a full mock harness for the comment-orchestration logic (fake
  github.rest.issues.listComments/createComment and github.graphql) and
  ran 8 end-to-end scenarios covering: fresh suggestion, idempotent no-op
  on unchanged classification, minimize-and-repost on changed
  classification, silent on non-release-worthy titles, and the
  "no-release-needed" transition in both directions. This caught a real
  bug before it ever reached CI: the "already said none, still none"
  case incorrectly reposted because the idempotency check compared
  against a hardcoded string instead of the full classification --
  fixed and reverified all 8 scenarios pass.
- Confirmed the baseline resolves correctly against the actual current
  repo state: main now has real tags (v1.0.0-beta.1, v1.0.0-beta.2), so
  `git describe --tags` resolves directly against origin/main rather
  than the PR branch; the .tabularium fallback remains for the
  no-tags-yet case this repo has already moved past.
- Both workflow YAML files re-validated after every edit.

Also added contributor-facing docs (README's "Contributing: PR Titles &
Versioning" section, cross-referenced from CLAUDE.md) so future
maintainers understand the convention without reading the workflow YAML.

Standard verification: cargo build/test (82/82)/clippy/fmt all pass, no
Cargo.lock drift, markdownlint clean across the whole repo, both workflow
files parse as valid YAML.

Rebased onto main after #5 merged (adds the pr-title job and release
validate job this PR builds on) — no functional changes beyond the
rebase itself, CHANGELOG merge conflict resolved by keeping both
entries.
aesslinger added a commit that referenced this pull request Aug 11, 2026
A precursor to eventually automating tag/release creation: when a PR's
title implies a version bump, CI now posts a comment suggesting the next
tag/version — without creating anything. Several real design decisions,
each resolved with the user rather than assumed:

- Type -> bump mapping: standard Conventional Commits (feat->minor,
  fix/refactor/perf->patch, docs/style/chore/test/ci/build->none,
  !/BREAKING CHANGE->major).
- Beta-phase behavior: while the baseline version carries a -beta.N/-rc.N
  suffix, a release-worthy PR just increments that stage's counter, not a
  full patch/minor/major recompute -- nothing has shipped stable yet.
- Prerelease channel: NOT signaled in the title. Checked precedent first
  (semantic-release/release-please control channel via labels or release-
  train branches, not embedded title syntax; no Tabularis repo does this
  either) and settled on a required prerelease:alpha|beta|rc|stable PR
  label. Missing label is a hard error -- the job fails rather than
  guessing a default channel.
- Version baseline: latest git tag on main, falling back to main's
  .tabularium version field if no tag exists (true right now).
- "Meaningful change" trigger: only re-comments when the PR's *derived
  classification* (type+breaking+channel) changes, not on every title
  edit -- tracked via a hidden HTML marker in the comment body, robust
  across opened/edited/reopened/synchronize/labeled/unlabeled.
- Marking old suggestions outdated: GitHub's real minimizeComment GraphQL
  mutation with classifier: OUTDATED -- the same action available via the
  web UI's "..." menu -> "Hide comment" -> "Outdated".
- Found and fixed a real trigger gap: the existing pull_request: block had
  no types:, defaulting to opened/synchronize/reopened -- a title-only
  edit never even re-ran CI. Widened to include edited/labeled/unlabeled
  (shared by all pull_request-triggered jobs, including the existing
  pr-title job, which also now re-validates on edited).

Checked precedent before implementing: no sibling Tabularis plugin repo
or tabularis itself has anything like this. A separate internal repo has
a fuller PR-title-driven auto-tag/auto-release pipeline; decided against
porting that whole pipeline here since it's a much larger behavioral
change (replaces manual tagging entirely) with no Tabularis-org
precedent -- this stays comment-only.

Verified thoroughly before trusting the logic in CI:
- Extracted the embedded classification regex and ran it in a real bash
  subshell (not zsh, which handles BASH_REMATCH differently) against 8
  title cases -- all classified correctly, including a deliberately
  unparseable title correctly erroring.
- Extracted the embedded version-arithmetic JS and unit-tested it
  standalone against 7 cases (beta-counter increment, stage transitions,
  graduation to stable from both beta and rc, stable->prerelease
  restart) -- all passed.
- Wrote a full mock harness for the comment-orchestration logic (fake
  github.rest.issues.listComments/createComment and github.graphql) and
  ran 8 end-to-end scenarios covering: fresh suggestion, idempotent no-op
  on unchanged classification, minimize-and-repost on changed
  classification, silent on non-release-worthy titles, and the
  "no-release-needed" transition in both directions. This caught a real
  bug before it ever reached CI: the "already said none, still none"
  case incorrectly reposted because the idempotency check compared
  against a hardcoded string instead of the full classification --
  fixed and reverified all 8 scenarios pass.
- Confirmed the baseline resolves correctly against the actual current
  repo state: main now has real tags (v1.0.0-beta.1, v1.0.0-beta.2), so
  `git describe --tags` resolves directly against origin/main rather
  than the PR branch; the .tabularium fallback remains for the
  no-tags-yet case this repo has already moved past.
- Both workflow YAML files re-validated after every edit.

Also added contributor-facing docs (README's "Contributing: PR Titles &
Versioning" section, cross-referenced from CLAUDE.md) so future
maintainers understand the convention without reading the workflow YAML.

Standard verification: cargo build/test (82/82)/clippy/fmt all pass, no
Cargo.lock drift, markdownlint clean across the whole repo, both workflow
files parse as valid YAML.

Rebased onto main after #5 merged (adds the pr-title job and release
validate job this PR builds on) — no functional changes beyond the
rebase itself, CHANGELOG merge conflict resolved by keeping both
entries.
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