ci(ci): require a changelog entry beside an engine change - #336
Conversation
The case for an Unreleased section is that each PR writes its own line while the person who made the change still remembers the caveat. Nothing enforced that, and at release time a missing entry looks exactly like a change that did not need one -- only the author can tell the difference, and only while the PR is open. A PR touching src/smda/ now has to touch CHANGELOG.md, or carry the no-changelog label for the cases that genuinely need no entry. File-level only, deliberately: a PR that edits an older entry rather than Unreleased passes. That is an acceptable false negative for a check whose job is to be loud rather than clever. pull_request rather than pull_request_target, since it needs the changed- file list and no secrets. The job runs unconditionally and decides inside the step rather than skipping on an if, so the check always reports a real result and the reason a PR is exempt lands in the log. labeled/unlabeled are among the triggers so applying the label re-runs the check instead of leaving a stale failure behind.
|
Reviewed against the workflow file rather than the table, and it is built to what #323 settled. The two choices you called out are the right ones and worth keeping. A job that runs unconditionally and decides inside the step is the only shape that reports a real result — a skipped job is precisely the "neither passing nor failing" ambiguity the check exists to remove — and having
Two non-blocking notes, neither worth a change now:
Taking it as-is. The |
The one from #323 that is not on the original list, and the one you said lands first.
What it enforces
A PR touching
src/smda/**also has to touchCHANGELOG.md, or carry theno-changeloglabel. Everything else passes untouched.Built to your specifics:
pull_requestrather thanpull_request_target, since it needs the changed-file list and no secrets, so the read-only token a fork PR receives is enough and no PR content is checked out or executed. File-level only — a PR that edits an older entry rather thanUnreleasedpasses, which is the acceptable false negative for a check whose job is to be loud rather than clever.Two choices worth stating because they are not obvious from the diff:
The job runs unconditionally and decides inside the step, rather than skipping on an
if. A skipped job reports as neither passing nor failing, which is exactly the ambiguity this check exists to remove — and it puts the reason a PR is exempt in the log, where the next reader looks.labeledandunlabeledare among the triggers, so applying the label re-runs the check instead of leaving a stale failure behind.The label is matched with
contains()against the label array rather than a joined string, so a label carrying a comma, or one this name is a prefix of, cannot be mistaken for it.Verified
Driven locally against a stubbed file list, all eight outcomes:
src/smda/intel/x.py,tests/t.pysrc/smda/intel/x.py,CHANGELOG.mdsrc/smda/intel/x.pyno-changelogtests/t.py,.github/workflows/ci.ymlsrc/smda/cil/y.py,docs/CHANGELOG.mdsrc/smda/aarch64/z.py(deletion)src/smdax/q.pyzizmor --persona=regularreports no findings, which is whatsecurity.ymlgates on. Full suite 2,110 passed, 1 skipped, 2,596 subtests;ruffclean;make typecheckexit 0 at master's own diagnostic count. No Python changes.Two things to know before merging
It needs the
no-changeloglabel to exist — you said you would create it. Referencing one that does not exist is harmless (the match simply never fires), so the order does not matter, but the escape hatch is not usable until it is there.It changes what #327, #329 and #335 need. All three touch
src/smda/**and none touchesCHANGELOG.md, so once this and the format PR are both in, each needs anUnreleasedentry on its next push. That is the intended demonstration rather than a problem — they are v4.7.0's content — and I will add the three entries once the format PR lands, unless you would rather write them.It is also not a required check until you make it one in branch protection; until then it reports without blocking.
Closes nothing on its own — #323 stays open for the
docs(docs)format PR, which I will raise against an emptyUnreleasednext.