fix: Validate the release-notes rewrite before publishing #80
No reviewers
Labels
No labels
priority/P0
priority/P1
priority/P2
priority/P3
size/L
size/M
size/S
size/XL
size/XS
state/blocked
state/done
state/in-progress
state/in-review
state/needs-refinement
state/ready
type/bug
type/chore
type/feature
type/refactor
No milestone
No project
No assignees
2 participants
Notifications
Due date
No due date set.
Dependencies
No dependencies set
Reference
amtronics/platform-actions!80
Loading…
Reference in a new issue
No description provided.
Delete branch "feature/79-validate-release-notes"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Issue
Closes #79
Problem
ahmad/imamahv0.7.1 was published with notes reading "I don't see any commit subjects in your message. Could you please provide the list…". Two faults met: the release was a forced rebake over an empty commit range, so the summariser was asked to summarise nothing; and the step accepted any non-empty answer, so a question passed for release notes.Solution
An empty range no longer calls the summariser — the notes say plainly that nothing changed and name the release being republished. A summary is accepted only when it carries the headings and bullets the prompt asks for; a question, an apology or a refusal is a failed rewrite and takes the commit-derived notes, with
notes-fallbackset and the rejecting rule named in the log. The prompt now also tells the model to output nothing when it cannot produce notes.Review notes
Four behaviour cases cover it, two of them driving the real refusal text from the imamah release. The README gains the rule under The contract, in the general form: a deterministic fallback, a shape check on the generated version, and the fallback whenever the check fails.
The imamah release itself has already been corrected by hand; this stops it recurring.
Risks and trade-offs
The shape check is deliberately structural rather than semantic: it cannot tell good notes from bad ones, only notes from not-notes. A valid summary that omits every heading would now fall back — acceptable, since the prompt demands them and the fallback is always true.
WIP: fix: Validate the release-notes rewrite before publishingto fix: Validate the release-notes rewrite before publishingOne finding, inline, non-blocking — an adjacent case the fix leaves in the state that produced
#79. Everything the PR claims for itself is verified.Verified by running the suite and mutating each guard, at
4dad26f3:Three guards, three cases, each killing exactly one — so none of them is resting on another, which is the failure mode the last two of these had.
REFUSALis the string that shipped asahmad/imamahv0.7.1's public notes, so the case proves the fix against the input that caused the issue rather than against a plausible stand-in. That is worth keeping as the shape for this kind of fix.SUBJECTSis now computed before the summariser and an empty one skips the call entirely — so the model is never asked to summarise nothing, which is what produced a question instead of notes. The shape check is the second line of defence rather than the fix, and the PR describes it that way.FALLBACK=falseon the empty-range branch is right, and I checked what the flag means before saying so: it signals a failed rewrite, and on an empty range no rewrite was needed. The deterministic sentence naming the republished version is a better answer than the commit list would be.set -euo pipefailquestion on the new unguardedSUBJECTS="$(git log …)"resolves clean:RANGEis either"${PREVIOUS}..HEAD"withPREVIOUSfromgit describe— so the tag exists locally — orHEAD. Neither makesgit logexit non-zero, so the assignment cannot tripset -e.validate).@ -178,0 +180,4 @@# nothing to summarise. Asked to summarise nothing, a model answers# about the absence of input, and that answer was once published as the# notes of a release (#79). Say plainly that nothing changed instead.SUBJECTS="$(git log --format='%s' ${RANGE})"SUBJECTSis the unfiltered log whileNOTESone line above is the filtered one, and that asymmetry leaves one case in exactly the state#79was about.A force-bump over a range whose commits are all excluded types has non-empty
SUBJECTS, so it takes the summariser path — and the model is handed the very subjects the project has decided are not user-facing, to rewrite as user-facing notes. I ran it rather than reasoned it, with agitstub returningchore: Bump depsanddocs: Fix a typo:It is reachable by the same path as
#79and no other:BUMP=nonefor a chore-only range, so nothing releases unlessforce-bump: patch— the rebake dispatch. So the two cases are siblings.#79was force-bump over an empty range; this is force-bump over a range with nothing releasable in it, and the project's ownEXCLUDE_TYPESrule is the thing saying so.What happens then is not obviously wrong — a model will turn "chore: Bump deps" into "### Improvements — Updated some dependencies", which passes the shape check and is arguably better than the fallback, which here is the bare
"See the commit history.". So I am raising the inconsistency rather than asserting a defect: the fix establishes that "nothing user-facing changed" deserves a plain sentence instead of a summarisation, and then applies it only to one of the two ways that can be true.If you want the consistent version, it is the filtered set that decides:
That also removes the second
git logcall over the same range, and it makes the existing "an empty range never asks a model to summarise nothing" case cover both ways of being empty — worth a second case naming the excluded-only range, since after the change one deletion would no longer kill both.If you would rather keep it as is — chores described in plain language beat
"See the commit history."— then say so in the comment aboveSUBJECTS, because the next reader will meet a filteredNOTESand an unfilteredSUBJECTStwo lines apart and will assume one of them is a bug.4dad26f3958cc1c15beaApproved. The finding is closed, and the fix answers the part I left open rather than the part I asked about.
I raised the asymmetry —
SUBJECTSunfiltered,NOTESfiltered — and offered the one-line version. What landed is better: it keeps the two "nothing to say" cases distinguishable instead of collapsing them, so a rebake over an empty range still says "No changes to the application. This release republishes vX." while a rebake over a chore-only range says "No user-facing changes in this release." Those are different facts, and the version I proposed would have merged them.Verified by running and mutating, at
9077f50d:printf '%s' "${SUBJECTS}" | node -eat line 208, whereSUBJECTSisRAW_SUBJECTSminus^(${EXCLUDE_RE}):. The two greps use the right anchors for their two formats —^- (…)for the- %snotes list,^(…)for the bare%ssubjects — and the chore/docs stub proves it rather than my reading it: the filtered result comes out empty, so both lines matched.|| trueon the new grep is necessary and present: underset -euo pipefailagrep -vthat filters everything exits 1, which is exactly the excluded-only case.RAW_SUBJECTSdistinction reaches the log line too, so an operator sees which of the two happened without reading the notes.This was my finding, and I want to be accurate about its weight: nothing was broken. A model handed
chore: Bump depswould have produced plausible notes about dependency bumps, which is not wrong so much as beside the point the project's ownEXCLUDE_TYPESmakes. What the fix buys is that the rule is now stated once and applied in both places.