📝 fix(release): AUD-S02 regenerate CHANGELOG + wire the drift gate (RELSE-01) - #22
Merged
Conversation
…et form The body template emitted `## v0.1.0 - 2026-08-05` while the hand-written historical stub in the footer uses `## [0.0.0] - 2026-08-04`. Two heading shapes in one generated file, and neither matches the Keep a Changelog format the header claims the file follows. Release surface: CHANGELOG.md is a published contract artifact; the heading text of every released section changes on the next regeneration. REQ-AUD-S02-01
… value (D-120) AUD-S04 changed what `pins.toolDigest` *is*: pre-D-120 records pin sha256 over the tool version string, post-D-120 records pin a Go build-info content digest. Same field, same type, same frozen v1alpha1 schema — different value, so the two never compare equal. Nothing in the published artifacts said so, and a record consumer diffing digests across the boundary would read "different build". The note lives in the cliff.toml header rather than in CHANGELOG.md because the changelog is regenerated in full from commit history: a hand-edit would be wiped by the next `task changelog-write` and the drift gate would go green with the warning gone. Release surface: CHANGELOG.md is a published contract artifact.
…arity Failing first, for the right reason: CHANGELOG.md has no [0.1.0] section and no task or workflow invokes hack/release/verify-changelog.sh. Every wiring assertion is a function over a file path and is re-run against a temp copy with the wired line deleted, so an assertion that cannot go red fails the script instead of shipping green. Each block extraction is positive-controlled (non-empty, anchored on a known-present line, bounded before the next block). REQ-AUD-S02-01, REQ-AUD-S02-02
hack/release/verify-changelog.sh shipped in E9-S03 and was invoked by no task and no workflow. CHANGELOG.md consequently lost its released section entirely after the v0.1.0 tag with nothing going red. Local: a check: entry immediately after compare-exitgate-test, so the first commit that leaves CHANGELOG.md stale reds the next task check. CI: a step in the verify job, guarded off pull_request. On that event actions/checkout builds refs/pull/N/merge — a merge commit minted at CI time whose subject git-cliff renders through the catch-all parser, a line no committed CHANGELOG.md can contain. Verified locally by merging a probe branch and observing the merge subject in the generated output. Push-to-main and the weekly schedule check out the real commit and are the backstop. Recorded as D-125. Release surface: CHANGELOG.md is a published contract artifact. REQ-AUD-S02-02
…124) readme_smoke_test.sh and truthlag_pins_test.sh shipped green, mutation- proven, and invoked by nothing — a manual gate, which is the defect class AUD-S06 exists to close. A README or docs edit could reopen DOC-07 or any of the 18 pins with nothing going red. New `docs-gates` task running both, listed sequentially in check: rather than as a deps: entry — go-task runs deps in parallel and the smoke test builds a binary while fmt is rewriting the tree. Both scripts advertised "NOT YET WIRED INTO ANY GATE"; that statement is now false, so it is replaced. The smoke test still skips `task …` lines from the README quick-start — that skip is what stops task check from calling itself, and its comment now says so.
hack/lint/depguard_test.sh is the adversarial half of the D-123 boundary control — it builds a violating module outside the repo from the repo`s real .golangci.yml and requires depguard to report it. Nothing invoked it, so deleting `- depguard` from .golangci.yml passed every wired gate: `task lint` only ever proved the clean polarity. task check only, not verify.yaml: the CI lint step is golangci-lint-action, which does not leave the binary on PATH for later steps, and this gate refuses to skip when golangci-lint is missing (by design — it is the boundary control).
…its cost The spec asked for the drift gate on pull_request too. Reproduced locally why that cannot work: git-cliff renders merge subjects through the catch-all parser, and on pull_request actions/checkout builds refs/pull/N/merge, so the generated changelog always contains a line no committed CHANGELOG.md can hold. Logged with the walk-back, the consequence, and the working rule for regeneration commits.
…utside it hack/release/README.md carried a "Not in `task check`" section arguing the gate must stay out of the local loop. AUD-S02 put it in, so that paragraph became a false claim in the same file as the tooling it describes. Replaced with where the gate actually runs, the one-commit- behind behaviour it implies, the chore(release): prefix that keeps a regeneration commit from creating fresh drift, and why CI skips it on pull_request (D-125). Adds changelog_gate_test.sh to the task table and records that cliff.toml`s header, not CHANGELOG.md, is where long-lived consumer notes live.
… commits The committed file was generated before the v0.1.0 tag existed: it had no released section at all, and 62 post-tag commits were still filed under Unreleased. Regenerated from cliff.toml, so [0.1.0] - 2026-08-05 is now a real section and Unreleased holds only what came after it. Also picks up the bracket heading form and the D-120 pins.toolDigest compatibility note from the changelog header. This commit is prefixed chore(release): — one of the two subjects cliff.toml skips — so regenerating does not itself create fresh drift. Release surface: CHANGELOG.md is a published contract artifact. REQ-AUD-S02-01
changelog_gate_test.sh is the only thing that reds when the `- task: changelog-verify` line is deleted from check:, and it was invoked by no task and no workflow — the exact defect this lane exists to close, shipped by the lane closing it. Proven: with that line deleted, `task release-changelog-gate-test` fails with "task check does not run changelog-verify"; nothing else in the gate set notices. Listed after changelog-verify because it probes the real CHANGELOG.md (append, assert red, trap restore), so a restore failure cannot red the simpler gate for the wrong reason. Deliberately NOT added to the script`s own WIRED_TASKS list: a script cannot assert its own invocation — deleting the check: line stops it running at all. Follows the release-verify-tag-gate-test precedent (AUD-S03).
The note said the fallback applies when build info is absent, matching the schema description verbatim. canonicalBuildInfo also falls back when build info is PRESENT but not content-bearing — no module sum and no vcs.revision, i.e. `go build -buildvcs=false` and test binaries — which is the branch a record consumer is more likely to actually meet.
…mmits Picks up this lane`s commits under Unreleased and the tightened D-120 toolDigest fallback wording from the changelog header. Prefixed chore(release): — one of the two subjects cliff.toml skips — so the regeneration does not itself leave the file stale. Release surface: CHANGELOG.md is a published contract artifact.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Closes audit finding RELSE-01 — the last of three release conditions for the next tag
(S01
2702941and S036bf55baare already merged).Why
CHANGELOG.mdhad no real[0.1.0]section and the drift gate was wired nowhere — it regressedonce already after the v0.1.0 tag.
task changelog-verifyis red onmaintoday; that is the defect.What
CHANGELOG.mdregenerated:## [0.1.0] - 2026-08-05present, post-tag commits under## Unreleased.changelog-verifywired intotask check, plus a drift-gate step in theverifyjob.hack/release/changelog_gate_test.shproving the wiring and the failing polarity.Judgment call (b): a documented walk-back, reproduced not argued
The spec asked for the CI step on both placements. The PR-scoped half turns out to be
structurally impossible, and the reviewer reproduced it independently: on
pull_request,actions/checkoutchecks outrefs/pull/N/merge— a merge commit minted at CI time — andcliff.toml's catch-all parser renders merge commits. Since the gate diffs the whole generatedchangelog, no committed file could ever contain a line naming a SHA that did not exist when it was
written. A PR-scoped step is red by construction, with no fix available to the author, and would
force commit SHAs into the changelog contrary to D-101. (Corroboration: the committed changelog
already contains a rendered
Merge remote-tracking branch …line, and pinned git-cliff 2.13.1 has nomerge-skip knob.)
Landed with the
release-exitgateguard precedent already in that file, recorded as D-125 withthe residual stated plainly: PR CI does not catch changelog drift;
task checkcatches it one commitlater by construction, and push-to-main is the backstop.
Three inherited obligations, all closed
toolDigestnote. AUD-S04 changed the value ofpins.toolDigestin everyrecord. A hand-written note in
CHANGELOG.mdwould be silently wiped by the next regenerationwhile every gate stayed green — so it lives in
cliff.toml's header and renders in, with the gateasserting presence in both files. Verified regeneration-durable, and the wording verified true
against
run.go(the fallback triggers when build info is absent or carries no main-modulecontent identity).
hack/lint/depguard_test.shwired (AUD-S07 shipped it unwired). The hole was real and thereviewer confirmed it: deleting
- depguardfrom.golangci.ymlleavestask lintgreen with"0 issues" while the polarity test now goes red.
hack/docs/{readme_smoke_test,truthlag_pins}_test.shwired per D-124, with both scripts'now-false "NOT YET WIRED" headers corrected and the anti-recursion branch retained.
The lane also found its own gate test was unwired — deleting the changelog wiring reddened
nothing until
release-changelog-gate-testwas added.Verification
task check(no recursion),task changelog-verify,task docs-build,CI=true go test ./...,actionlint, and everyhack/**script green. The new gate test passes under Linux GNU grep 3.11 +mawk as well as macOS — this epic produced several defects visible only on the CI platform. Four
wirings independently broken by the reviewer and confirmed red.
Follow-ups recorded, none blocking: one grep in the gate test is vacuous because the token appears in
permanent commit subjects (the load-bearing assertion beside it does fire); the wiring assertions
check that a task mentions its script, not that failure propagates, so an appended
|| truewouldpass.
Refs: RELSE-01, D-125, D-124, D-120, D-101, D-016/E9-S03 · REQ-AUD-S02-01/02 · Release surface