# 0237 — The release commit holds release metadata only

> Source: https://github.com/max-listov/stitchkit/blob/master/docs/decisions/0237-release-commit-holds-metadata-only.md

---
# 0237 — The release commit holds release metadata only

**Status:** Accepted
**Date:** 2026-10-05

Builds on ADR 0136. Invariants I8 and I14.

## Decision

A release tag points at the head of the default branch. That head is the
`release(train): <summary> in X.Y.Z` commit of the train, or fix commits with their own
conventional types stacked directly on it.

The release commit holds release metadata only: package versions, changelogs, `release-train.json`,
the lockfile, the promoted migration sections and the maturity-cadence sentences that rolling a
changelog moves. It is never empty. Every feature and fix is a separate commit before it, with a
conventional subject (`feat(scope): …`, `fix(scope): …`) and a body that says what changed and why.

After a red candidate, the repair is a commit with its own type on the release branch. The tagged
head must still have a green exact-SHA push run, so a repair needs a green run of its own before
the tag. An empty commit is refused wherever it stands. A repeated `release(train)` subject over
source changes is not a release commit and is refused.

The rule is enforced at three places that share one implementation (`scripts/release-subject.ts`):
`pre-push` for a pushed release commit, `release:train` before the first tag, and the publishing
workflow's `assert-subject` step.

## Why

Every commit used to wear the release subject so that the tag could sit on it. A release
collapsed into one commit of unrelated changes, `git bisect` and `git revert` could only act on a
whole release, repairs needed release subjects, and one empty commit carried a tag. The tag needs
the exact-SHA CI evidence and a head the release commit shaped; it does not need every change to be
named after the release.

## Consequences

- `git log` of a release shows one commit per change and one metadata commit.
- A release commit that carries source or documentation changes is refused before it is pushed.
- The window searched below the tagged head is 50 first-parent commits.

## Amendment — the work is not split per change

The release commit's rule stands: it holds release metadata only. The sentence "every feature and
fix is a separate commit before it" is withdrawn: a release's work is committed before the release
commit as one commit, or as many as the work needs, each with a conventional subject
(`feat(scope): …`, `fix(scope): …`) and a body that says what changed and why. Nothing refuses a
release for how its work was divided; `scripts/release-subject.ts` refuses only a release commit that
carries more than metadata.
