Scope
Release-record review of the published tag v0.6.0-rc1 (commit ad6470c, tag object 243ddaca30c5933f094144955bd54ed1afa80c23), read from a clean read-only worktree at that tag and cross-checked against the live GitHub Release (isDraft: false, isPrerelease: true, publishedAt: 2026-08-14T20:13:59Z).
Two defects in the same record, filed together because the first one caused the shape of the second's neighbourhood: the tag's documentation of what shipped.
Problem
1 (actionable). changelog.d/752.fixed.md is still an unfolded fragment at v0.6.0-rc1. The fix it documents — granting python-gates the issues: write that parity.yml statically needs, without which the tag push failed the entire run with startup_failure and published nothing — is in the tagged tree, but appears in no CHANGELOG.md section and in no release body. The assembler already ships the gate that would have caught this (assemble_changelog.py --require-empty), and it is wired into zero workflows.
2 (cosmetic). The published release body's fourth Highlights bullet says "Seven commands" and then names six items, five of which are commands.
Evidence
1. The fragment is unfolded at the tag
changelog.d/ at v0.6.0-rc1 holds exactly two files:
$ ls changelog.d/
752.fixed.md
README.md
changelog.d/752.fixed.md, verbatim (opening):
- **A `v*` tag no longer fails the whole release run with `startup_failure`
before any job starts.** `release.yml`'s `python-gates` called
`parity.yml` granting only `permissions: {contents: read}`, but
`parity.yml`'s `notify-planner-drift` job statically declares
`permissions: {issues: write}`. A called workflow may never exceed its
caller's grant, and GitHub evaluates that BEFORE any job runs -- regardless
of the `if:` that keeps `notify-planner-drift` to `repository_dispatch`, so
a tag build that never reaches the job still failed on it. The v0.6.0-rc1
tag reproduced it exactly: run `31830969596`, `startup_failure`, zero jobs,
no release object and no assets. `python-gates` now grants `issues: write`
alongside `contents: read`; a sweep of every local caller/callee pair
reports zero remaining conflicts.
changelog.d/752.fixed.md:1-13
The convention the fragment violates, from the directory's own README:
Create `changelog.d/<issue>.<category>.md`:
changelog.d/README.md:23
## Release time
`python/scripts/assemble_changelog.py` folds every fragment into
`CHANGELOG.md` under the current `Unreleased` header, in canonical section
order, then deletes the fragments:
changelog.d/README.md:50-54
The fix itself IS in the tagged tree
python-gates:
uses: ./.github/workflows/parity.yml
with:
python_only: true
permissions:
contents: read
# `issues: write` is required even though NOTHING here writes an issue.
.github/workflows/release.yml:262-268
.github/workflows/release.yml:281
Why it was missed: the commit landed after the assembler ran
$ git log --oneline -8 v0.6.0-rc1
ad6470c Merge pull request #753 from alplabai/dev
3f39f34 fix(ci): grant python-gates the issues:write parity.yml statically needs (#752)
93d97d0 Merge pull request #751 from alplabai/dev
e2f5021 release: v0.6.0-rc1 (#750)
The assembler ran at e2f5021 release: v0.6.0-rc1 (#750). 3f39f34 landed after it and before the tag, so its fragment was never folded. Any commit between the release commit and the tag can reopen this hole — that is the general defect, not #752 specifically.
Nothing in CHANGELOG.md or the release body records it
$ grep -c "startup_failure" CHANGELOG.md
0
$ sed -n '8p' CHANGELOG.md
## [0.6.0-rc1] — 2026-08-14
The release body inherits the hole by construction — it is sliced from that same section:
- name: Slice CHANGELOG section for the release notes
.github/workflows/release.yml:676
Its two guards (::error::CHANGELOG.md has no \## [{version}]` sectionand::error::CHANGELOG.md's `## [{version}]` section is empty) both pass here — the section exists and is non-empty, it is merely missing one entry. The published ## Fixed` list carries eight bullets (#719, #727, #721, #569, #736, #734, #494, #749); #752 is not among them.
The gate already exists and is unwired
ap.add_argument("--require-empty", action="store_true",
help="exit 1 if any fragment remains unfolded (for a release gate)")
python/scripts/assemble_changelog.py:173-174
if args.require_empty and total:
print(
"::error::unfolded changelog fragments remain -- run "
"`python3 python/scripts/assemble_changelog.py` and commit "
"the result before tagging",
file=sys.stderr,
)
return 1
python/scripts/assemble_changelog.py:194-201
But nothing calls it:
$ grep -rn "require-empty\|changelog.d\|assemble_changelog" .github/
$ echo $?
1
Zero hits — no workflow invokes the assembler at all, in any mode. And --check cannot substitute for it; the script says so itself:
ap.add_argument("--check", action="store_true",
help="report what would be folded; change nothing; exit 0 always "
"(this is informational, NOT a gate -- see --require-empty)")
python/scripts/assemble_changelog.py:170-172
2. "Seven commands", six items
Live release body (gh release view v0.6.0-rc1 --json body), Highlights bullet 4, verbatim:
- **Seven commands stopped reporting success for something they did not do** — `flash`, `doctor`, `quality`, `init`, `monitor` and the Flow D planner.
Named items: flash, doctor, quality, init, monitor, "the Flow D planner" — six, of which five are commands. The ## Fixed list below it carries eight bullets, seven of them command-behaviour fixes with doctor counted twice (#727 and #736), which is the likely origin of "seven".
This one is not in CHANGELOG.md — the ## Highlights section exists only in the published body:
$ grep -n "Seven commands\|## Highlights" CHANGELOG.md
$ echo $?
1
The two [Ss]even occurrences inside the rc1 section are both correct and unrelated: CHANGELOG.md:202 "drives seven commands deeply (--version, bootstrap, init, doctor, build, generate, examples)" = exactly seven, and CHANGELOG.md:841 "Seven diagnostics ... on sdk current, examples, presets, sdk list --online, clean, support-bundle and inspect" = exactly seven.
Impact
Documentation record only — no shipped-binary behaviour changes, nothing installs or builds differently, and severity is low for both halves.
For (1) the concrete harm is history corruption on the next release, not this one: left in place, the assembler folds 752.fixed.md into the following version's section, misdating a fix that shipped in v0.6.0-rc1 — the exact drift the one-file-per-change convention exists to prevent. Meanwhile the tag's own record is incomplete: a CI fix that had to land for this release to publish at all is documented nowhere in [0.6.0-rc1] or the release page. Considered and rejected as an explanation: CI-only CHANGELOG entries are the established norm here, not an exclusion (CHANGELOG.md:50 planner-resync.yml, :1156 and :2270 release.yml), and the fragment is not a stray — its text matches the code at release.yml:262-281 exactly.
For (2) the harm is that a reader counting the named items against the stated number cannot tell whether a command was omitted. Cosmetic, with zero behavioural, install or security consequence — but it is the release's own summary line for its largest fix batch, on a public page.
Blast radius narrowed on both: the finder cited CHANGELOG.md:8 for (2), which is only the ## [0.6.0-rc1] — 2026-08-14 header — the sentence is not in the file, so this is a gh release edit fix and not a worktree edit. The finder's release.yml:262-270 citation for (1) is also off by the comment block's length; the grant is at :281.
Suggested fix
- Fold
changelog.d/752.fixed.md into CHANGELOG.md's ## [0.6.0-rc1] ### Fixed list and delete the fragment. If amending a published section is unwanted, put it in the next version's section with an explicit "shipped in v0.6.0-rc1 after the notes were assembled" note.
- Wire the gate that already exists: add a step to
.github/workflows/release.yml running python3 python/scripts/assemble_changelog.py --require-empty at the tagged commit, before the "Slice CHANGELOG section for the release notes" step at :676. No new tooling is needed — --require-empty is implemented at assemble_changelog.py:173-201 and is currently referenced by no workflow. This is what closes the general hole, since the assembler runs at the release commit and any later commit can reopen it.
gh release edit v0.6.0-rc1 to make the Highlights bullet's count match its list — either "Six surfaces" with the six items as written, or name the missing seventh. Nothing in the repo at this tag needs changing for this half.
Acceptance criteria
changelog.d/ contains nothing but README.md at any tagged commit, enforced by a workflow step, not by convention.
- Every fix present in a tag's tree is present in that tag's
CHANGELOG.md section.
- The published
v0.6.0-rc1 Highlights bullet's stated count matches the items it names.
Scope
Release-record review of the published tag
v0.6.0-rc1(commitad6470c, tag object243ddaca30c5933f094144955bd54ed1afa80c23), read from a clean read-only worktree at that tag and cross-checked against the live GitHub Release (isDraft: false,isPrerelease: true,publishedAt: 2026-08-14T20:13:59Z).Two defects in the same record, filed together because the first one caused the shape of the second's neighbourhood: the tag's documentation of what shipped.
Problem
1 (actionable).
changelog.d/752.fixed.mdis still an unfolded fragment atv0.6.0-rc1. The fix it documents — grantingpython-gatestheissues: writethatparity.ymlstatically needs, without which the tag push failed the entire run withstartup_failureand published nothing — is in the tagged tree, but appears in noCHANGELOG.mdsection and in no release body. The assembler already ships the gate that would have caught this (assemble_changelog.py --require-empty), and it is wired into zero workflows.2 (cosmetic). The published release body's fourth Highlights bullet says "Seven commands" and then names six items, five of which are commands.
Evidence
1. The fragment is unfolded at the tag
changelog.d/atv0.6.0-rc1holds exactly two files:changelog.d/752.fixed.md, verbatim (opening):changelog.d/752.fixed.md:1-13The convention the fragment violates, from the directory's own README:
changelog.d/README.md:23changelog.d/README.md:50-54The fix itself IS in the tagged tree
.github/workflows/release.yml:262-268.github/workflows/release.yml:281Why it was missed: the commit landed after the assembler ran
The assembler ran at
e2f5021 release: v0.6.0-rc1 (#750).3f39f34landed after it and before the tag, so its fragment was never folded. Any commit between the release commit and the tag can reopen this hole — that is the general defect, not #752 specifically.Nothing in CHANGELOG.md or the release body records it
The release body inherits the hole by construction — it is sliced from that same section:
.github/workflows/release.yml:676Its two guards (
::error::CHANGELOG.md has no \## [{version}]` sectionand::error::CHANGELOG.md's `## [{version}]` section is empty) both pass here — the section exists and is non-empty, it is merely missing one entry. The published## Fixed` list carries eight bullets (#719, #727, #721, #569, #736, #734, #494, #749); #752 is not among them.The gate already exists and is unwired
python/scripts/assemble_changelog.py:173-174python/scripts/assemble_changelog.py:194-201But nothing calls it:
Zero hits — no workflow invokes the assembler at all, in any mode. And
--checkcannot substitute for it; the script says so itself:python/scripts/assemble_changelog.py:170-1722. "Seven commands", six items
Live release body (
gh release view v0.6.0-rc1 --json body), Highlights bullet 4, verbatim:Named items:
flash,doctor,quality,init,monitor, "the Flow D planner" — six, of which five are commands. The## Fixedlist below it carries eight bullets, seven of them command-behaviour fixes withdoctorcounted twice (#727 and #736), which is the likely origin of "seven".This one is not in
CHANGELOG.md— the## Highlightssection exists only in the published body:The two
[Ss]evenoccurrences inside the rc1 section are both correct and unrelated:CHANGELOG.md:202"drives seven commands deeply (--version,bootstrap,init,doctor,build,generate,examples)" = exactly seven, andCHANGELOG.md:841"Seven diagnostics ... onsdk current,examples,presets,sdk list --online,clean,support-bundleandinspect" = exactly seven.Impact
Documentation record only — no shipped-binary behaviour changes, nothing installs or builds differently, and severity is low for both halves.
For (1) the concrete harm is history corruption on the next release, not this one: left in place, the assembler folds
752.fixed.mdinto the following version's section, misdating a fix that shipped inv0.6.0-rc1— the exact drift the one-file-per-change convention exists to prevent. Meanwhile the tag's own record is incomplete: a CI fix that had to land for this release to publish at all is documented nowhere in[0.6.0-rc1]or the release page. Considered and rejected as an explanation: CI-only CHANGELOG entries are the established norm here, not an exclusion (CHANGELOG.md:50planner-resync.yml,:1156and:2270release.yml), and the fragment is not a stray — its text matches the code atrelease.yml:262-281exactly.For (2) the harm is that a reader counting the named items against the stated number cannot tell whether a command was omitted. Cosmetic, with zero behavioural, install or security consequence — but it is the release's own summary line for its largest fix batch, on a public page.
Blast radius narrowed on both: the finder cited
CHANGELOG.md:8for (2), which is only the## [0.6.0-rc1] — 2026-08-14header — the sentence is not in the file, so this is agh release editfix and not a worktree edit. The finder'srelease.yml:262-270citation for (1) is also off by the comment block's length; the grant is at:281.Suggested fix
changelog.d/752.fixed.mdintoCHANGELOG.md's## [0.6.0-rc1]### Fixedlist and delete the fragment. If amending a published section is unwanted, put it in the next version's section with an explicit "shipped in v0.6.0-rc1 after the notes were assembled" note..github/workflows/release.ymlrunningpython3 python/scripts/assemble_changelog.py --require-emptyat the tagged commit, before the "Slice CHANGELOG section for the release notes" step at:676. No new tooling is needed —--require-emptyis implemented atassemble_changelog.py:173-201and is currently referenced by no workflow. This is what closes the general hole, since the assembler runs at the release commit and any later commit can reopen it.gh release edit v0.6.0-rc1to make the Highlights bullet's count match its list — either "Six surfaces" with the six items as written, or name the missing seventh. Nothing in the repo at this tag needs changing for this half.Acceptance criteria
changelog.d/contains nothing butREADME.mdat any tagged commit, enforced by a workflow step, not by convention.CHANGELOG.mdsection.v0.6.0-rc1Highlights bullet's stated count matches the items it names.