Skip to content

📝 fix(release): AUD-S02 regenerate CHANGELOG + wire the drift gate (RELSE-01) - #22

Merged
konih merged 12 commits into
mainfrom
worktree-agent-a5948133e050f795b
Aug 7, 2026
Merged

📝 fix(release): AUD-S02 regenerate CHANGELOG + wire the drift gate (RELSE-01)#22
konih merged 12 commits into
mainfrom
worktree-agent-a5948133e050f795b

Conversation

@konih

@konih konih commented Aug 7, 2026

Copy link
Copy Markdown
Contributor

Closes audit finding RELSE-01 — the last of three release conditions for the next tag
(S01 2702941 and S03 6bf55ba are already merged).

Why

CHANGELOG.md had no real [0.1.0] section and the drift gate was wired nowhere — it regressed
once already after the v0.1.0 tag. task changelog-verify is red on main today; that is the defect.

What

  • CHANGELOG.md regenerated: ## [0.1.0] - 2026-08-05 present, post-tag commits under ## Unreleased.
  • changelog-verify wired into task check, plus a drift-gate step in the verify job.
  • hack/release/changelog_gate_test.sh proving 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/checkout checks out refs/pull/N/merge — a merge commit minted at CI time — and
cliff.toml's catch-all parser renders merge commits. Since the gate diffs the whole generated
changelog, 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 no
merge-skip knob.)

Landed with the release-exitgate guard precedent already in that file, recorded as D-125 with
the residual stated plainly: PR CI does not catch changelog drift; task check catches it one commit
later by construction, and push-to-main is the backstop.

Three inherited obligations, all closed

  • The 🔴 tag-blocking toolDigest note. AUD-S04 changed the value of pins.toolDigest in every
    record. A hand-written note in CHANGELOG.md would be silently wiped by the next regeneration
    while every gate stayed green — so it lives in cliff.toml's header and renders in, with the gate
    asserting 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-module
    content identity).
  • hack/lint/depguard_test.sh wired (AUD-S07 shipped it unwired). The hole was real and the
    reviewer confirmed it: deleting - depguard from .golangci.yml leaves task lint green with
    "0 issues"
    while the polarity test now goes red.
  • hack/docs/{readme_smoke_test,truthlag_pins}_test.sh wired 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-test was added.

Verification

task check (no recursion), task changelog-verify, task docs-build, CI=true go test ./...,
actionlint, and every hack/** 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 || true would
pass.

Refs: RELSE-01, D-125, D-124, D-120, D-101, D-016/E9-S03 · REQ-AUD-S02-01/02 · Release surface

konih added 12 commits August 7, 2026 15:05
…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.
@konih
konih merged commit 7247e6d into main Aug 7, 2026
8 checks passed
@konih
konih deleted the worktree-agent-a5948133e050f795b branch August 7, 2026 13:43
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant