chore(release): v0.26.0 #327

Merged
quartermaster merged 2 commits from release-prep/rolling into main 2026-07-05 16:46:23 +02:00
Member

Added

CI test suite as required merge gate — new .forgejo/workflows/tests.yml runs full bats tests/ + shellcheck --severity=warning on every PR + push:main. ADR-0001 named shellcheck as CI gate; this workflow implements it. One pre-existing SC1010 false-positive in scripts/lib/forgejo-api.sh (jq variable named do) fixed by rename to merge_do (#330).

docs/SECURITY.md — new page documenting the release-toolkit trust boundary + the audit-scope gap between consumer-pinning and secondary-checkout script execution. Honest disclosure of the current state; architectural fix targets v1.1.0 (#336).

docs/VERSIONING.md — new page memorializing the v1.0.0 SemVer stability contract: v1.x preserves backward compat on workflow inputs, config schema, published script CLI, and documented behavior contracts; breaking changes require v2.0.0; deprecation cycle spans a minor before removal; tag immutability commitment; post-v1.0.0 cadence shift to boring/settled infrastructure (#338).

New docs/cold-read-adopter.md brings the full-project adopter-simulation cold-read prompt in-repo (previously BookStack-only) — the prompt used to prime outside-model reviews of the whole toolkit. docs/cold-read-prompt.md is renamed to docs/cold-read-changelog.md (distinguisher-leads naming, matching its # Changelog Cold-Read Prompt heading); inbound references updated (#366).

Mechanized pre-merge register grep-gate (scripts/register-check.sh +
reusable-register-check.yml). Case-insensitive sweep across
scripts/ + .forgejo/ + tests/ + docs/ + changelog.d/ +
README.md + CHANGELOG.md + AGENTS.md for chamber-name attribution

  • invented-jargon vocabulary. Fails PR merges on hits; extensible
    pattern list (bash array) + .register-allowlist for load-bearing
    exceptions. Closes #392.

Empirical rationale: manual per-round sweeps don't stay closed
under continued PR throughput. Round-4 alone saw n≥3 same-day
re-seedings across concurrent-authorship PRs — careful authors
crediting reviewers inline is the recurring failure mode. Manual
sweep as a discipline is falsified at this cadence; CI grep-gate is
the fix by construction.

Adopter wiring: drop .forgejo/workflows/register-check.yml
invoking frankenbit/release-toolkit/.forgejo/workflows/reusable-register-check.yml@v1.0.0
on your pull_request: branches: [main] trigger. Allow-list at
.register-allowlist (glob patterns; directory-prefix dir/ for
recursive allow) opts out load-bearing exceptions like
compiled-changelog historical records or maintainer archaeology.

Changed

Adopter-visible header comments tightened — top-of-file comment blocks in reusable-mirror-to-codeberg.yml and toolkit-self's release.yml scrubbed of internal-review vocabulary ("substrate-of-record", "dogfoods its own new mechanic") + shortened. Consumer wiring pattern + design references preserved; canonical vocabulary in ADRs untouched. Set J adopter-hygiene sweep (#313).

BREAKING (v1.0.0-blocking): publish_mode default flipped from immediate back to draft. Cold-read finding: README advertised "gates every release behind a human" but v0.10.0 immediate-default let the merge of the rolling PR publish directly, no Gate-3 click. The fix restores draft as the default so the advertised safety property holds by default. Adopters who value frictionless CI cadence over the per-cut Gate-3 opt in via publish_mode: 'immediate' at the workflow_call layer. Adopters currently relying on the immediate-default must explicitly opt in when bumping to this release. (#332)

docs/integration.md — tighten the version-file-basename scope note: the two #252 refs (now closed as superseded) point at the broader-ecosystem tracker #337, which covers TOML (pyproject/Cargo), Maven, Helm, and other release-please-shaped ecosystems. Also names a workable escape hatch for unlisted ecosystems today (version_files: [] + consumer hook).

docs/integration.md — new Token Decision Matrix (3 yes/no questions → adopter's token setup with minimum scope), Sample failure modes section naming the 4 concrete errors adopters actually hit (Repository not found, push_whitelist pre-flight FAIL, case-sensitive push-denied, mergeable=blocked on manifest PR), Bot user provisioning step-by-step for path α (Forgejo user + collaborator + PAT scope + whitelist + secret), and a concrete secrets: inherit wiring example. Retires the "token model is complicated" trust-dent surfaced by external cold-read round 2 (#356).

README.md + docs/integration.md — new Runner requirements section naming explicitly what the toolkit assumes: Debian/Ubuntu-family runner with apt-get + sudo for install-on-demand of yq. Per-requirement table names root cause + what to do if your runner is non-Debian / rootless / ephemeral (pre-bake yq+jq+curl so the install step is a no-op). Configurable install commands + auto-skip logic named as v1.1.0+ target. Retires the "workflow installs deps with apt-get/sudo, not universally safe" trust-dent surfaced by external cold-read round 2 (#357).

docs/integration.md — new Tag creation section documenting the mechanism explicitly: Forgejo release API creates the tag via tag_name + target_commitish (no git tag && git push --tags); tag points at the rolling-PR merge commit; idempotency behavior enumerated (same-tag-same-commit skips; same-tag-different-commit fails loud; same-tag-on-Forgejo-missing-local skips via pre-check); cross-variant status per Forgejo/Codeberg/Gitea. Retires the "delegated tag creation needs proof" trust-dent surfaced by external cold-read round 2 (#359).

scripts/release-decide.sh + scripts/lib/conventional-commits.sh + docs/integration.md + README.md + examples/README.md + tests — BREAKING for the fresh-repo failure surface, no regression for repos with a real anchor: fresh consumer repo (no manifest, no stable tag) now bootstraps from a virtual v0.0.0 anchor and walks from the initial commit (#368). First cut produces v0.1.0 (any feat: since root), v0.0.1 (only fix:), or mode=noop (only chore:/docs:/refactor: — chore isn't release-relevant per conventional-commits; adopter adds a feat:/fix: or drops a bump/* label to force the cut). SUPERSEDES the #355 Option B fail-loud path shipped in v0.25.0: adopters no longer need to manually tag v0.0.0 before the first cut. cc_list_commits_since gains a --root sentinel so the virtual-anchor bump-walk stays root-inclusive even when a prerelease tag exists (prerelease v-tags matched git describe --match 'v*' and would silently drop the root feat: from the bump walk despite step-2 seeing it). A feat!: / BREAKING CHANGE at root bumps to v1.0.0 by default; set pre_v1_breaking_to_minor: true for the pre-1.0 opt-in that downgrades to v0.1.0. Advanced adopters can still hand-write .release-toolkit-manifest.json; that path is unchanged.

docs/VERSIONING.md + docs/SECURITY.md — retire the "as of v1.0.0" / "target v1.1.0" future-tense-in-current-adopter-surface leaks external cold-read round 3 flagged. VERSIONING.md rewritten to describe current pre-1.0 state (Option A) + a "will commit at v1.0.0" preamble frames the effective-at-v1.0.0 sections without ambiguity. SECURITY.md's architectural-fix section labeled "Planned for v1.1.0" with an explicit callout that it describes not-yet-shipped work (Option B — preserves the fix-target framing). Example pin swapped from @v1.0.0 (aspirational) → @v0.25.0 (current). Tag-immutability section reframed as "already active" (the discipline was already in force pre-1.0, not a v1.0.0 addition). Adopters reading v0.25.0 docs no longer confuse future contract with current guarantee (#369).

Adopter-facing docs read register-clean — internal review-process language (per-reviewer attributions, ratify ceremony) scrubbed from CHANGELOG.md + docs/adr/*.md, so the project no longer reads as internally process-driven. Technical content and issue/ADR anchors are preserved and the documented cold-read editorial discipline is kept + glossed; only the internal-review narration is removed. Third register-audit delta after #352 + #362 (#372).

README.md + docs/integration.md — new Positioning section declaring release-toolkit Codeberg-primary (source, releases, and tags at codeberg.org/frankenbit/release-toolkit) and targeting Forgejo Actions as its CI runtime, with a runtime-compatibility list (Forgejo Actions tested; Gitea Actions untested; Codeberg where a runner is available). This is the definitive answer to the adopter positioning question external cold-read raised (rounds 2–4): where the project lives (Codeberg) and what CI runtime it targets (Forgejo Actions) are stated as separate facts so neither is ambiguous. The maintainers develop on a self-hosted Forgejo and mirror code + releases to Codeberg (#358, #397).

Fixed

examples/README.md + examples/{go,node}-project/.forgejo/workflows/*.yml — bumped stale @v0.16.0 pins to the current released tag @v0.25.0, closing the trust-dent surfaced by external cold-read round 2 (adopters landing on stale copy-paste refs). Extended scripts/hooks/update-doc-version-refs.sh to walk examples/**/*.yml + examples/README.md on every future cut so the drift can't recur; the hook silently no-ops when a consumer doesn't have an examples/ directory (#311).

Mirror workflow: empty-TAG_NAME guard as Forgejo-agnostic first-check. Forgejo Actions doesn't reliably honor needs.<reusable-call-job>.outputs in a job-level if: gate (empirical: mirror fired on non-cut push:main events), causing spurious workflow failures on every non-cut push despite if: mode == 'cut'. Script-level guard exits cleanly on empty TAG_NAME + sidesteps the outputs-based gating question; wrong-commit class was already closed by the tag-guard (#322).

SemVer parser now enforces the full SemVer 2.0.0 grammar — leading zeros in numeric identifiers, empty prerelease/build identifiers, and leading/trailing dots are rejected at parse time. Previous regex accepted these invalid values and then crashed on bump (bash-octal interpretation of 1.2.0808: value too great for base). Base-10 arithmetic (10#$var) added to semver_bump as belt-and-suspenders. 20 new test cases cover every invalid-input class from the cold-read finding (#329).

Squash-merge cut detection — PREP_SUBJECT_RE in release-decide.sh now accepts both chore(release): prepare vX.Y.Z (merge/rebase style) and chore(release): vX.Y.Z (Forgejo squash-merge style using PR title). Layer 2 branch-source-check remains the belt-and-suspenders gate. Adopters following standard Git squash-merge conventions were silently losing their release cut before this fix (#331).

Token docs cleanup — removed all references to the unimplemented release_token (path β) that shipped in docs but never wired in the workflow. Rewrote the no-token path (γ) section: dedicated PAT (path α) is operationally required for repos with required-checks branch protection, not merely optional. Path (γ) fits only repos where main accepts unrestricted merges. Docs now match actual workflow behavior (#333).

Config schema version now enforced against supported set — config_validate in scripts/lib/config.sh maintains SUPPORTED_SCHEMA_VERSIONS=(1) and rejects unsupported versions with a clear error naming what's supported. Previously any integer passed (version: 999 silently accepted). Forward-compat: when schema v2 arrives, add 2 to the array. +4 test cases (#335).

scripts/repin.sh no longer defaults REPIN_REVIEWER to an internal reviewer username. The default is now empty and the PR review-request is skipped when unset, so an adopter running repin.sh no longer opens a re-pin PR requesting review from a user that doesn't exist in their repo. Set REPIN_REVIEWER=<user> to request a specific reviewer. (#340)

README.md + docs/integration.md + docs/SECURITY.md + docs/events-logging.md + docs/VERSIONING.md + docs/migration/tmux-tell.md + ADRs 0001/0002/0003 — v1.0.0 pre-cut Cold-Read delta (#353): quick-start publish_mode example dropped so the reusable's draft default applies (was contradicting the "gates every release" claim, same class as #332); ADR-0003 now records the v1.0.0 supersession — the 2026-06-26 immediate-default amendment is retired, and the default-record matches the shipped code; adopter-facing docs no longer cite AGENTS.md (maintainer doc) — replaced with public references to the CI gate + inlined content; ADRs no longer link alcatraz-infra#65 (cross-repo private tracker) — replaced with plain-prose "initial architecture proposal"; docs/VERSIONING.md glosses paths α/γ + mode = noop|update|cut with links to ADR-0007 / ADR-0004; docs/migration/tmux-tell.md reframed as a pointer + tmux-tell-specific notes (drops "Status: stub" language).

scripts/release-decide.sh + README.md + docs/integration.md + examples/README.mdBREAKING for docs-honesty, no behavior regression: fresh consumer repo (no .release-toolkit-manifest.json AND no stable vX.Y.Z tag) previously failed with a terse FATAL: no stable vX.Y.Z tag found; docs implied fresh repos "just worked" — external cold-read round 2 named this as the biggest trust-dent for first-user experience (#355). Fixed by (a) making the error actionable — names the exact git tag -a v0.0.0 && git push origin v0.0.0 command, points at the docs section, mentions the manifest hand-write escape hatch — and (b) documenting the anchor requirement upfront: new "Bootstrapping a new consumer repo" section in docs/integration.md naming both anchor options; README quick-start now has a "step 0" for the tag; examples/README.md post-copy checklist ends with the bootstrap command. Behavior unchanged: no code path that previously succeeded now fails; only the failure surface (message + docs coverage) improved.

.forgejo/workflows/reusable-release.yml + scripts/lib/forgejo-api.sh + scripts/lib/config.sh + docs/integration.md — layered default-branch resolution replaces the hardcoded "main" at the manifest follow-up PR path. New resolve_default_branch helper in forgejo-api.sh walks 6 fallback layers: (1) RT_DEFAULT_BRANCH env var, (2) default_branch: field in release-toolkit.yml, (3) TOOLKIT_DEFAULT_BRANCH_HINT env var (workflow YAML passes ${{ github.event.repository.default_branch }}), (4) Forgejo API GET /repos/{owner}/{repo}, (5) git symbolic-ref refs/remotes/origin/HEAD, (6) fail-loud with actionable error naming all 5 preceding layers. New config_get_default_branch in config.sh exposes layer 2. Adopters on non-main default branches (trunk/master/develop/custom) previously hit a silent-corruption class: manifest PR opened against a nonexistent branch or one with unrelated content. Full docs in docs/integration.md § Default branch (#370). Regression tests cover all 6 layers (#370).

scripts/lib/forgejo-api.sh + scripts/setup-bump-labels.sh — keep the FORGEJO_TOKEN out of curl's argv (visible via /proc/<pid>/cmdline + ps on some systems). All 6 previously-argv-exposing curl invocations refactored to use a 0600-mode auth config file (install -m600, -K "$auth_config"), matching the pattern already established in reusable-mirror-to-codeberg.yml:184-193. New shared helper _forgejo_auth_config_new in the lib consolidates the mktemp+install+write dance across the 3 lib callsites; setup-bump-labels.sh inlines the same shape at script start with a trap cleanup. Closes the argv-vs-config-file inconsistency between the two curl surfaces on the same substrate (#371).

.forgejo/workflows/reusable-release.yml + scripts/release-prep.sh + scripts/repin.sh + scripts/manifest-precheck.sh — semantic-class sweep for hardcoded default-branch references missed by #370's literal-main sweep. Path-α direct-push (2 sites) + git_push_authed helper (1 site) + repin PR base + manifest-precheck remote-read: all now use resolve_default_branch (6-layer chain) or RT_DEFAULT_BRANCH env var. Adopters on non-main default branches (trunk/master/develop/custom) no longer hit silent-corruption at any push callsite. New tests/hardcoded-defaults-sweep.bats (7 grep-guards) locks the class-axis so future edits can't reintroduce the anti-patterns (#379).

scripts/release-decide.sh + docs/integration.md.release-toolkit-manifest.json anchor precedence rule for the last_released_sha vs last_released_tag disagreement class. When both fields are set AND the local tag exists AND its SHA disagrees with last_released_sha, release-decide.sh now fails loud with an actionable error naming both SHAs + three resolution options (tag authoritative / manifest authoritative / re-bootstrap by deleting manifest). Shallow clones and tags-pruned setups where the tag isn't local are treated as "cannot verify" — the check is skipped rather than erroring, so those adopters aren't blocked. Docs section explains the three divergence scenarios (force-push after cut / manual tag surgery / cross-repo mirror desync) + why v1.0.0 ships fail-loud rather than silent-manifest-wins (#380).

.forgejo/workflows/reusable-release.yml + scripts/release-prep.sh — all git -c http.extraheader="Authorization: token $TOKEN" push argv-token exposures refactored to write the extraheader to a mktemp+chmod 600 config file scoped via GIT_CONFIG_GLOBAL. Same semantic class as #371 (curl argv-token → config file) and alcatraz-infra#89 (token in git remote URL): grep-sweep-missed-sibling because those sweeps targeted their specific literal patterns, not the semantic class of "token visible outside process env." Token now stays in the mode-0600 file + env (process-owned), never in argv — invisible to ps auxww + auditd audit logs. Regression test in tests/hardcoded-defaults-sweep.bats (grep-guard on git -c ... http.extraheader pattern) locks the class-axis (#381).

scripts/draft-release.sh header comment (lines 1-8) refreshed to describe current v1.0.0 behavior: publish_mode default is draft (per #332) not immediate as the pre-#332 header claimed; the "cut path merges to main" phrasing now points at the consumer's resolved default branch (per #370) rather than hardcoding main (#383).

docs/integration.md § Dry-run preview rewritten to accurately describe the three-script dry-run flow (release-decide.sh / release-prep.sh / draft-release.sh) — what mutates locally (release-prep.sh transitions CHANGELOG + bumps version_files in place), what emits summaries (draft-release.sh Forgejo API METHOD POST / URL / BODY), what skips (git remote writes + Forgejo API calls). Corrects the pre-fix "still needs an origin remote" note: origin is NOT needed for the git-log walk (release-decide.sh reads last_released_sha from the LOCAL manifest + walks LOCAL history); origin IS used for OWNER/REPO derivation (dry-run substitutes OWNER/REPO placeholder if missing) + Forgejo API calls (dry-run summarizes). Remote-less clones work for dry-run (#385).

  • security+workflow: semantic-class sweep for hardcoded-main + argv-token (#379 + #381) (#386)
  • workflow: move #370 env vars to correct step + wire config_get + docstrings (Surveyor 9a16)
  • tests: preserve v0.10.0+ default marker in mutation-guard fixture

Internal

Docs reclassify — docs/cold-read/, docs/drift-audit-*, and docs/walkthrough-* moved to docs/internal/. Adopter-facing root docs/ now contains only canonical adopter documentation (integration, conventions, operations, architecture, adr, migration); maintainer audits + walkthrough artifacts live under docs/internal/ with a bucket-README explaining the split (#312).

Workflow comments scrubbed of internal incident-history — the .forgejo/workflows/*.yml docblocks and inline comments no longer carry accumulated bug-archaeology (probe-iteration logs, dated cross-repo incident recoveries, version-specific cut-failure narration, internal review-vocabulary). Load-bearing invariants and issue/ADR anchors are kept; only the narration is removed, so the reusable workflows read as auditable machinery rather than internal history (#340).

Script, workflow, and test comment blocks scrubbed of internal review-role attributions (per-reviewer names + short hex-id tags). Round-3 (#372) closed the prose surface (README + docs/ + ADRs + CHANGELOG); this closes the sibling code-comment surface — scripts/*.sh + .forgejo/workflows/*.yml + tests/*.bats — that an adopter reads when auditing the toolkit. Load-bearing technical invariants and issue/ADR anchors are kept; only the attribution narration is removed, with no behavior change (one test fixture genericized behavior-preservingly, mutation-verified green). (#384)

AGENTS.md (contributor pre-flight doc) scrubbed of internal review-role attributions + invented-jargon — the last manual surface in the register-audit sweep (sibling to #384). Per-reviewer name+hex attributions removed from the disciplines (the guidance content is kept), the historical convergence-arc section trimmed of its internal narration (catch-count table + forward-going guidance kept), and a functional reviewer-identity default genericized. Round-3 held this doc as maintainer-surface; round-4 external reached it, so it's covered now (#391).

docs/internal/ — moved the point-in-time maintainer archaeology (the 2026-06-27 consumer-walkthrough audit and documentation-drift audit) out of the source tree to the maintainers' internal wiki, and cleaned the remaining cold-read/ framework of internal-review-role names so a source-tree browser sees a neutral external-doc-review framework. Part of the Codeberg-primary shift: source-tree visibility is now the adopter-facing surface, so point-in-time internal-process artifacts are archived off-tree while the active cold-read/ maintainer tool stays in-repo, provenance preserved (#398).

### Added CI test suite as required merge gate — new `.forgejo/workflows/tests.yml` runs full `bats tests/` + `shellcheck --severity=warning` on every PR + push:main. ADR-0001 named shellcheck as CI gate; this workflow implements it. One pre-existing SC1010 false-positive in `scripts/lib/forgejo-api.sh` (jq variable named `do`) fixed by rename to `merge_do` (**#330**). `docs/SECURITY.md` — new page documenting the release-toolkit trust boundary + the audit-scope gap between consumer-pinning and secondary-checkout script execution. Honest disclosure of the current state; architectural fix targets v1.1.0 (**#336**). `docs/VERSIONING.md` — new page memorializing the v1.0.0 SemVer stability contract: v1.x preserves backward compat on workflow inputs, config schema, published script CLI, and documented behavior contracts; breaking changes require v2.0.0; deprecation cycle spans a minor before removal; tag immutability commitment; post-v1.0.0 cadence shift to boring/settled infrastructure ([#338](https://git.frankenbit.de/frankenbit/release-toolkit/issues/338)). New `docs/cold-read-adopter.md` brings the full-project adopter-simulation cold-read prompt in-repo (previously BookStack-only) — the prompt used to prime outside-model reviews of the whole toolkit. `docs/cold-read-prompt.md` is renamed to `docs/cold-read-changelog.md` (distinguisher-leads naming, matching its `# Changelog Cold-Read Prompt` heading); inbound references updated (**#366**). Mechanized pre-merge register grep-gate (`scripts/register-check.sh` + `reusable-register-check.yml`). Case-insensitive sweep across `scripts/` + `.forgejo/` + `tests/` + `docs/` + `changelog.d/` + `README.md` + `CHANGELOG.md` + `AGENTS.md` for chamber-name attribution + invented-jargon vocabulary. Fails PR merges on hits; extensible pattern list (bash array) + `.register-allowlist` for load-bearing exceptions. Closes #392. **Empirical rationale**: manual per-round sweeps don't stay closed under continued PR throughput. Round-4 alone saw n≥3 same-day re-seedings across concurrent-authorship PRs — careful authors crediting reviewers inline is the recurring failure mode. Manual sweep as a discipline is falsified at this cadence; CI grep-gate is the fix by construction. **Adopter wiring**: drop `.forgejo/workflows/register-check.yml` invoking `frankenbit/release-toolkit/.forgejo/workflows/reusable-register-check.yml@v1.0.0` on your `pull_request: branches: [main]` trigger. Allow-list at `.register-allowlist` (glob patterns; directory-prefix `dir/` for recursive allow) opts out load-bearing exceptions like compiled-changelog historical records or maintainer archaeology. ### Changed Adopter-visible header comments tightened — top-of-file comment blocks in `reusable-mirror-to-codeberg.yml` and toolkit-self's `release.yml` scrubbed of internal-review vocabulary ("substrate-of-record", "dogfoods its own new mechanic") + shortened. Consumer wiring pattern + design references preserved; canonical vocabulary in ADRs untouched. Set J adopter-hygiene sweep (**#313**). **BREAKING (v1.0.0-blocking):** `publish_mode` default flipped from `immediate` back to `draft`. Cold-read finding: README advertised "gates every release behind a human" but v0.10.0 immediate-default let the merge of the rolling PR publish directly, no Gate-3 click. The fix restores `draft` as the default so the advertised safety property holds by default. Adopters who value frictionless CI cadence over the per-cut Gate-3 opt in via `publish_mode: 'immediate'` at the workflow_call layer. Adopters currently relying on the immediate-default must explicitly opt in when bumping to this release. (**#332**) `docs/integration.md` — tighten the version-file-basename scope note: the two `#252` refs (now closed as superseded) point at the broader-ecosystem tracker [#337](https://git.frankenbit.de/frankenbit/release-toolkit/issues/337), which covers TOML (pyproject/Cargo), Maven, Helm, and other release-please-shaped ecosystems. Also names a workable escape hatch for unlisted ecosystems today (`version_files: []` + consumer hook). `docs/integration.md` — new **Token Decision Matrix** (3 yes/no questions → adopter's token setup with minimum scope), **Sample failure modes** section naming the 4 concrete errors adopters actually hit (`Repository not found`, `push_whitelist pre-flight FAIL`, case-sensitive push-denied, `mergeable=blocked` on manifest PR), **Bot user provisioning** step-by-step for path α (Forgejo user + collaborator + PAT scope + whitelist + secret), and a **concrete `secrets: inherit` wiring example**. Retires the "token model is complicated" trust-dent surfaced by external cold-read round 2 ([#356](https://git.frankenbit.de/frankenbit/release-toolkit/issues/356)). `README.md` + `docs/integration.md` — new **Runner requirements** section naming explicitly what the toolkit assumes: Debian/Ubuntu-family runner with `apt-get` + `sudo` for install-on-demand of `yq`. Per-requirement table names root cause + what to do if your runner is non-Debian / rootless / ephemeral (pre-bake `yq`+`jq`+`curl` so the install step is a no-op). Configurable install commands + auto-skip logic named as v1.1.0+ target. Retires the "workflow installs deps with apt-get/sudo, not universally safe" trust-dent surfaced by external cold-read round 2 ([#357](https://git.frankenbit.de/frankenbit/release-toolkit/issues/357)). `docs/integration.md` — new **Tag creation** section documenting the mechanism explicitly: Forgejo release API creates the tag via `tag_name` + `target_commitish` (no `git tag && git push --tags`); tag points at the rolling-PR merge commit; idempotency behavior enumerated (same-tag-same-commit skips; same-tag-different-commit fails loud; same-tag-on-Forgejo-missing-local skips via pre-check); cross-variant status per Forgejo/Codeberg/Gitea. Retires the "delegated tag creation needs proof" trust-dent surfaced by external cold-read round 2 ([#359](https://git.frankenbit.de/frankenbit/release-toolkit/issues/359)). `scripts/release-decide.sh` + `scripts/lib/conventional-commits.sh` + `docs/integration.md` + `README.md` + `examples/README.md` + tests — **BREAKING for the fresh-repo failure surface, no regression for repos with a real anchor**: fresh consumer repo (no manifest, no stable tag) now bootstraps from a **virtual `v0.0.0` anchor** and walks from the initial commit ([#368](https://git.frankenbit.de/frankenbit/release-toolkit/issues/368)). First cut produces `v0.1.0` (any `feat:` since root), `v0.0.1` (only `fix:`), or `mode=noop` (only `chore:`/`docs:`/`refactor:` — chore isn't release-relevant per conventional-commits; adopter adds a `feat:`/`fix:` or drops a `bump/*` label to force the cut). SUPERSEDES the [#355](https://git.frankenbit.de/frankenbit/release-toolkit/issues/355) Option B fail-loud path shipped in v0.25.0: adopters no longer need to manually tag `v0.0.0` before the first cut. `cc_list_commits_since` gains a `--root` sentinel so the virtual-anchor bump-walk stays root-inclusive even when a prerelease tag exists (prerelease v-tags matched `git describe --match 'v*'` and would silently drop the root feat: from the bump walk despite step-2 seeing it). A `feat!:` / `BREAKING CHANGE` at root bumps to `v1.0.0` by default; set `pre_v1_breaking_to_minor: true` for the pre-1.0 opt-in that downgrades to `v0.1.0`. Advanced adopters can still hand-write `.release-toolkit-manifest.json`; that path is unchanged. `docs/VERSIONING.md` + `docs/SECURITY.md` — retire the "as of v1.0.0" / "target v1.1.0" future-tense-in-current-adopter-surface leaks external cold-read round 3 flagged. VERSIONING.md rewritten to describe **current** pre-1.0 state (Option A) + a "will commit at v1.0.0" preamble frames the effective-at-v1.0.0 sections without ambiguity. SECURITY.md's architectural-fix section labeled **"Planned for v1.1.0"** with an explicit callout that it describes not-yet-shipped work (Option B — preserves the fix-target framing). Example pin swapped from `@v1.0.0` (aspirational) → `@v0.25.0` (current). Tag-immutability section reframed as "already active" (the discipline was already in force pre-1.0, not a v1.0.0 addition). Adopters reading v0.25.0 docs no longer confuse future contract with current guarantee ([#369](https://git.frankenbit.de/frankenbit/release-toolkit/issues/369)). Adopter-facing docs read register-clean — internal review-process language (per-reviewer attributions, `ratify` ceremony) scrubbed from `CHANGELOG.md` + `docs/adr/*.md`, so the project no longer reads as internally process-driven. Technical content and issue/ADR anchors are preserved and the documented cold-read editorial discipline is kept + glossed; only the internal-review narration is removed. Third register-audit delta after #352 + #362 (**#372**). `README.md` + `docs/integration.md` — new **Positioning** section declaring release-toolkit **Codeberg-primary** (source, releases, and tags at [codeberg.org/frankenbit/release-toolkit](https://codeberg.org/frankenbit/release-toolkit)) and **targeting Forgejo Actions** as its CI runtime, with a runtime-compatibility list (Forgejo Actions tested; Gitea Actions untested; Codeberg where a runner is available). This is the definitive answer to the adopter positioning question external cold-read raised (rounds 2–4): *where the project lives* (Codeberg) and *what CI runtime it targets* (Forgejo Actions) are stated as separate facts so neither is ambiguous. The maintainers develop on a self-hosted Forgejo and mirror code + releases to Codeberg ([#358](https://git.frankenbit.de/frankenbit/release-toolkit/issues/358), [#397](https://git.frankenbit.de/frankenbit/release-toolkit/issues/397)). ### Fixed `examples/README.md` + `examples/{go,node}-project/.forgejo/workflows/*.yml` — bumped stale `@v0.16.0` pins to the current released tag `@v0.25.0`, closing the trust-dent surfaced by external cold-read round 2 (adopters landing on stale copy-paste refs). Extended `scripts/hooks/update-doc-version-refs.sh` to walk `examples/**/*.yml` + `examples/README.md` on every future cut so the drift can't recur; the hook silently no-ops when a consumer doesn't have an `examples/` directory ([#311](https://git.frankenbit.de/frankenbit/release-toolkit/issues/311)). Mirror workflow: empty-TAG_NAME guard as Forgejo-agnostic first-check. Forgejo Actions doesn't reliably honor `needs.<reusable-call-job>.outputs` in a job-level `if:` gate (empirical: mirror fired on non-cut push:main events), causing spurious workflow failures on every non-cut push despite `if: mode == 'cut'`. Script-level guard exits cleanly on empty TAG_NAME + sidesteps the outputs-based gating question; wrong-commit class was already closed by the tag-guard (**#322**). SemVer parser now enforces the full SemVer 2.0.0 grammar — leading zeros in numeric identifiers, empty prerelease/build identifiers, and leading/trailing dots are rejected at parse time. Previous regex accepted these invalid values and then crashed on bump (bash-octal interpretation of `1.2.08` → `08: value too great for base`). Base-10 arithmetic (`10#$var`) added to `semver_bump` as belt-and-suspenders. 20 new test cases cover every invalid-input class from the cold-read finding (**#329**). Squash-merge cut detection — `PREP_SUBJECT_RE` in `release-decide.sh` now accepts both `chore(release): prepare vX.Y.Z` (merge/rebase style) and `chore(release): vX.Y.Z` (Forgejo squash-merge style using PR title). Layer 2 branch-source-check remains the belt-and-suspenders gate. Adopters following standard Git squash-merge conventions were silently losing their release cut before this fix (**#331**). Token docs cleanup — removed all references to the unimplemented `release_token` (path β) that shipped in docs but never wired in the workflow. Rewrote the no-token path (γ) section: dedicated PAT (path α) is **operationally required** for repos with required-checks branch protection, not merely optional. Path (γ) fits only repos where `main` accepts unrestricted merges. Docs now match actual workflow behavior (**#333**). Config schema version now enforced against supported set — `config_validate` in `scripts/lib/config.sh` maintains `SUPPORTED_SCHEMA_VERSIONS=(1)` and rejects unsupported versions with a clear error naming what's supported. Previously any integer passed (`version: 999` silently accepted). Forward-compat: when schema v2 arrives, add 2 to the array. +4 test cases (**#335**). `scripts/repin.sh` no longer defaults `REPIN_REVIEWER` to an internal reviewer username. The default is now empty and the PR review-request is skipped when unset, so an adopter running `repin.sh` no longer opens a re-pin PR requesting review from a user that doesn't exist in their repo. Set `REPIN_REVIEWER=<user>` to request a specific reviewer. (**#340**) `README.md` + `docs/integration.md` + `docs/SECURITY.md` + `docs/events-logging.md` + `docs/VERSIONING.md` + `docs/migration/tmux-tell.md` + ADRs 0001/0002/0003 — v1.0.0 pre-cut Cold-Read delta ([#353](https://git.frankenbit.de/frankenbit/release-toolkit/issues/353)): quick-start `publish_mode` example dropped so the reusable's `draft` default applies (was contradicting the "gates every release" claim, same class as [#332](https://git.frankenbit.de/frankenbit/release-toolkit/issues/332)); ADR-0003 now records the v1.0.0 supersession — the 2026-06-26 immediate-default amendment is retired, and the default-record matches the shipped code; adopter-facing docs no longer cite `AGENTS.md` (maintainer doc) — replaced with public references to the CI gate + inlined content; ADRs no longer link `alcatraz-infra#65` (cross-repo private tracker) — replaced with plain-prose "initial architecture proposal"; `docs/VERSIONING.md` glosses paths α/γ + `mode = noop|update|cut` with links to ADR-0007 / ADR-0004; `docs/migration/tmux-tell.md` reframed as a pointer + tmux-tell-specific notes (drops "Status: stub" language). `scripts/release-decide.sh` + `README.md` + `docs/integration.md` + `examples/README.md` — **BREAKING for docs-honesty, no behavior regression**: fresh consumer repo (no `.release-toolkit-manifest.json` AND no stable `vX.Y.Z` tag) previously failed with a terse `FATAL: no stable vX.Y.Z tag found`; docs implied fresh repos "just worked" — external cold-read round 2 named this as the biggest trust-dent for first-user experience ([#355](https://git.frankenbit.de/frankenbit/release-toolkit/issues/355)). Fixed by (a) making the error actionable — names the exact `git tag -a v0.0.0 && git push origin v0.0.0` command, points at the docs section, mentions the manifest hand-write escape hatch — and (b) documenting the anchor requirement upfront: new "Bootstrapping a new consumer repo" section in `docs/integration.md` naming both anchor options; README quick-start now has a "step 0" for the tag; `examples/README.md` post-copy checklist ends with the bootstrap command. Behavior unchanged: no code path that previously succeeded now fails; only the failure surface (message + docs coverage) improved. `.forgejo/workflows/reusable-release.yml` + `scripts/lib/forgejo-api.sh` + `scripts/lib/config.sh` + `docs/integration.md` — layered default-branch resolution replaces the hardcoded `"main"` at the manifest follow-up PR path. New `resolve_default_branch` helper in forgejo-api.sh walks 6 fallback layers: (1) `RT_DEFAULT_BRANCH` env var, (2) `default_branch:` field in `release-toolkit.yml`, (3) `TOOLKIT_DEFAULT_BRANCH_HINT` env var (workflow YAML passes `${{ github.event.repository.default_branch }}`), (4) Forgejo API `GET /repos/{owner}/{repo}`, (5) `git symbolic-ref refs/remotes/origin/HEAD`, (6) fail-loud with actionable error naming all 5 preceding layers. New `config_get_default_branch` in config.sh exposes layer 2. Adopters on non-`main` default branches (trunk/master/develop/custom) previously hit a silent-corruption class: manifest PR opened against a nonexistent branch or one with unrelated content. Full docs in `docs/integration.md § Default branch (#370)`. Regression tests cover all 6 layers ([#370](https://git.frankenbit.de/frankenbit/release-toolkit/issues/370)). `scripts/lib/forgejo-api.sh` + `scripts/setup-bump-labels.sh` — keep the `FORGEJO_TOKEN` out of curl's argv (visible via `/proc/<pid>/cmdline` + `ps` on some systems). All 6 previously-argv-exposing curl invocations refactored to use a 0600-mode auth config file (`install -m600`, `-K "$auth_config"`), matching the pattern already established in `reusable-mirror-to-codeberg.yml:184-193`. New shared helper `_forgejo_auth_config_new` in the lib consolidates the mktemp+install+write dance across the 3 lib callsites; `setup-bump-labels.sh` inlines the same shape at script start with a `trap` cleanup. Closes the argv-vs-config-file inconsistency between the two curl surfaces on the same substrate ([#371](https://git.frankenbit.de/frankenbit/release-toolkit/issues/371)). `.forgejo/workflows/reusable-release.yml` + `scripts/release-prep.sh` + `scripts/repin.sh` + `scripts/manifest-precheck.sh` — semantic-class sweep for hardcoded default-branch references missed by #370's literal-`main` sweep. Path-α direct-push (2 sites) + `git_push_authed` helper (1 site) + repin PR base + manifest-precheck remote-read: all now use `resolve_default_branch` (6-layer chain) or `RT_DEFAULT_BRANCH` env var. Adopters on non-`main` default branches (trunk/master/develop/custom) no longer hit silent-corruption at any push callsite. New `tests/hardcoded-defaults-sweep.bats` (7 grep-guards) locks the class-axis so future edits can't reintroduce the anti-patterns ([#379](https://git.frankenbit.de/frankenbit/release-toolkit/issues/379)). `scripts/release-decide.sh` + `docs/integration.md` — `.release-toolkit-manifest.json` anchor precedence rule for the `last_released_sha` vs `last_released_tag` disagreement class. When both fields are set AND the local tag exists AND its SHA disagrees with `last_released_sha`, `release-decide.sh` now **fails loud** with an actionable error naming both SHAs + three resolution options (tag authoritative / manifest authoritative / re-bootstrap by deleting manifest). Shallow clones and tags-pruned setups where the tag isn't local are treated as "cannot verify" — the check is skipped rather than erroring, so those adopters aren't blocked. Docs section explains the three divergence scenarios (force-push after cut / manual tag surgery / cross-repo mirror desync) + why v1.0.0 ships fail-loud rather than silent-manifest-wins ([#380](https://git.frankenbit.de/frankenbit/release-toolkit/issues/380)). `.forgejo/workflows/reusable-release.yml` + `scripts/release-prep.sh` — all `git -c http.extraheader="Authorization: token $TOKEN" push` argv-token exposures refactored to write the extraheader to a `mktemp`+`chmod 600` config file scoped via `GIT_CONFIG_GLOBAL`. Same semantic class as [#371](https://git.frankenbit.de/frankenbit/release-toolkit/issues/371) (curl argv-token → config file) and [alcatraz-infra#89](https://git.frankenbit.de/frankenbit/alcatraz-infra/issues/89) (token in git remote URL): grep-sweep-missed-sibling because those sweeps targeted their specific literal patterns, not the semantic class of "token visible outside process env." Token now stays in the mode-0600 file + env (process-owned), never in argv — invisible to `ps auxww` + auditd audit logs. Regression test in `tests/hardcoded-defaults-sweep.bats` (grep-guard on `git -c ... http.extraheader` pattern) locks the class-axis ([#381](https://git.frankenbit.de/frankenbit/release-toolkit/issues/381)). `scripts/draft-release.sh` header comment (lines 1-8) refreshed to describe current v1.0.0 behavior: `publish_mode` default is `draft` (per [#332](https://git.frankenbit.de/frankenbit/release-toolkit/issues/332)) not `immediate` as the pre-#332 header claimed; the "cut path merges to main" phrasing now points at the consumer's resolved default branch (per [#370](https://git.frankenbit.de/frankenbit/release-toolkit/issues/370)) rather than hardcoding `main` ([#383](https://git.frankenbit.de/frankenbit/release-toolkit/issues/383)). `docs/integration.md § Dry-run preview` rewritten to accurately describe the three-script dry-run flow (`release-decide.sh` / `release-prep.sh` / `draft-release.sh`) — what mutates locally (release-prep.sh transitions CHANGELOG + bumps version_files in place), what emits summaries (draft-release.sh Forgejo API `METHOD POST / URL / BODY`), what skips (git remote writes + Forgejo API calls). Corrects the pre-fix "still needs an origin remote" note: origin is NOT needed for the git-log walk (release-decide.sh reads `last_released_sha` from the LOCAL manifest + walks LOCAL history); origin IS used for OWNER/REPO derivation (dry-run substitutes `OWNER/REPO` placeholder if missing) + Forgejo API calls (dry-run summarizes). Remote-less clones work for dry-run ([#385](https://git.frankenbit.de/frankenbit/release-toolkit/issues/385)). - **security+workflow**: semantic-class sweep for hardcoded-main + argv-token (#379 + #381) (#386) - **workflow**: move #370 env vars to correct step + wire config_get + docstrings (Surveyor 9a16) - **tests**: preserve v0.10.0+ default marker in mutation-guard fixture ### Internal Docs reclassify — `docs/cold-read/`, `docs/drift-audit-*`, and `docs/walkthrough-*` moved to `docs/internal/`. Adopter-facing root `docs/` now contains only canonical adopter documentation (integration, conventions, operations, architecture, adr, migration); maintainer audits + walkthrough artifacts live under `docs/internal/` with a bucket-README explaining the split (**#312**). Workflow comments scrubbed of internal incident-history — the `.forgejo/workflows/*.yml` docblocks and inline comments no longer carry accumulated bug-archaeology (probe-iteration logs, dated cross-repo incident recoveries, version-specific cut-failure narration, internal review-vocabulary). Load-bearing invariants and issue/ADR anchors are kept; only the narration is removed, so the reusable workflows read as auditable machinery rather than internal history (**#340**). Script, workflow, and test comment blocks scrubbed of internal review-role attributions (per-reviewer names + short hex-id tags). Round-3 (#372) closed the prose surface (README + docs/ + ADRs + CHANGELOG); this closes the sibling code-comment surface — `scripts/*.sh` + `.forgejo/workflows/*.yml` + `tests/*.bats` — that an adopter reads when auditing the toolkit. Load-bearing technical invariants and issue/ADR anchors are kept; only the attribution narration is removed, with no behavior change (one test fixture genericized behavior-preservingly, mutation-verified green). (**#384**) `AGENTS.md` (contributor pre-flight doc) scrubbed of internal review-role attributions + invented-jargon — the last manual surface in the register-audit sweep (sibling to #384). Per-reviewer name+hex attributions removed from the disciplines (the guidance content is kept), the historical convergence-arc section trimmed of its internal narration (catch-count table + forward-going guidance kept), and a functional reviewer-identity default genericized. Round-3 held this doc as maintainer-surface; round-4 external reached it, so it's covered now (**#391**). `docs/internal/` — moved the point-in-time maintainer archaeology (the 2026-06-27 consumer-walkthrough audit and documentation-drift audit) out of the source tree to the maintainers' internal wiki, and cleaned the remaining `cold-read/` framework of internal-review-role names so a source-tree browser sees a neutral external-doc-review framework. Part of the Codeberg-primary shift: source-tree visibility is now the adopter-facing surface, so point-in-time internal-process artifacts are archived off-tree while the active `cold-read/` maintainer tool stays in-repo, provenance preserved ([#398](https://git.frankenbit.de/frankenbit/release-toolkit/issues/398)).
forgejo-actions force-pushed release-prep/rolling from c31a29bdf4
All checks were successful
changelog-body-check / changelog body Cold-Read linter (pull_request) Successful in 4s
check-self-bootstrap / check (pull_request) Has been skipped
changelog-body-check / check (pull_request) Successful in 0s
fragment-check / changelog fragment-kind (pull_request) Successful in 4s
fragment-check / check (pull_request) Successful in 0s
manifest-check / manifest-vs-tag consistency (pull_request) Successful in 5s
manifest-check / check (pull_request) Successful in 0s
to 0f83bcdd0f 2026-07-03 13:03:20 +02:00
Compare
release-bot changed title from chore(release): v0.25.1 to chore(release): v0.26.0 2026-07-03 13:05:06 +02:00
forgejo-actions force-pushed release-prep/rolling from 0f83bcdd0f to 3e623e0f11 2026-07-03 13:05:08 +02:00 Compare
forgejo-actions force-pushed release-prep/rolling from 3e623e0f11 to adbf130d4e 2026-07-03 13:38:24 +02:00 Compare
forgejo-actions force-pushed release-prep/rolling from adbf130d4e to 9fb3b277e4 2026-07-03 13:51:21 +02:00 Compare
forgejo-actions force-pushed release-prep/rolling from 9fb3b277e4 to ec0f862784 2026-07-03 18:31:04 +02:00 Compare
forgejo-actions force-pushed release-prep/rolling from ec0f862784 to ad0f92ccf2 2026-07-03 18:40:54 +02:00 Compare
forgejo-actions force-pushed release-prep/rolling from ad0f92ccf2 to 2a2437dfcd 2026-07-03 18:44:35 +02:00 Compare
forgejo-actions force-pushed release-prep/rolling from 2a2437dfcd to 82ac8541a2 2026-07-03 18:58:52 +02:00 Compare
forgejo-actions force-pushed release-prep/rolling from 82ac8541a2 to b4fe4dc7a9 2026-07-03 19:17:43 +02:00 Compare
forgejo-actions force-pushed release-prep/rolling from b4fe4dc7a9 to 45204cad56 2026-07-03 19:23:59 +02:00 Compare
forgejo-actions force-pushed release-prep/rolling from 45204cad56 to 4f35d816ac 2026-07-03 19:24:12 +02:00 Compare
forgejo-actions force-pushed release-prep/rolling from 4f35d816ac to 573c154903 2026-07-03 22:34:25 +02:00 Compare
forgejo-actions force-pushed release-prep/rolling from 573c154903 to 1a0987ddcd 2026-07-03 22:55:42 +02:00 Compare
forgejo-actions force-pushed release-prep/rolling from 1a0987ddcd to 2bed47b1c6 2026-07-03 23:56:13 +02:00 Compare
forgejo-actions force-pushed release-prep/rolling from 2bed47b1c6 to 55f393d4f4 2026-07-04 00:01:47 +02:00 Compare
forgejo-actions force-pushed release-prep/rolling from 55f393d4f4 to 4c7068a3e4 2026-07-04 00:09:12 +02:00 Compare
forgejo-actions force-pushed release-prep/rolling from 4c7068a3e4 to 3d4a990e9a 2026-07-04 00:11:01 +02:00 Compare
forgejo-actions force-pushed release-prep/rolling from 3d4a990e9a to 21a9b39d5b 2026-07-04 00:14:55 +02:00 Compare
forgejo-actions force-pushed release-prep/rolling from 21a9b39d5b to eb8dba83c8 2026-07-04 09:39:02 +02:00 Compare
forgejo-actions force-pushed release-prep/rolling from eb8dba83c8 to 6566116180 2026-07-04 09:46:27 +02:00 Compare
forgejo-actions force-pushed release-prep/rolling from 6566116180 to 41eb823726 2026-07-04 09:52:07 +02:00 Compare
forgejo-actions force-pushed release-prep/rolling from 41eb823726 to b21871172e 2026-07-04 09:55:55 +02:00 Compare
forgejo-actions force-pushed release-prep/rolling from b21871172e to e1efb63881 2026-07-04 11:31:21 +02:00 Compare
forgejo-actions force-pushed release-prep/rolling from e1efb63881 to b51230af29 2026-07-04 20:50:55 +02:00 Compare
forgejo-actions force-pushed release-prep/rolling from b51230af29 to 26b512c073 2026-07-04 20:58:30 +02:00 Compare
forgejo-actions force-pushed release-prep/rolling from 26b512c073 to a9ad9615f2 2026-07-04 21:01:53 +02:00 Compare
forgejo-actions force-pushed release-prep/rolling from a9ad9615f2 to c37c2b5390 2026-07-04 22:04:10 +02:00 Compare
forgejo-actions force-pushed release-prep/rolling from c37c2b5390 to 157f2e8236 2026-07-04 22:20:30 +02:00 Compare
forgejo-actions force-pushed release-prep/rolling from 157f2e8236 to e2ae35a5dd 2026-07-04 23:04:09 +02:00 Compare
forgejo-actions force-pushed release-prep/rolling from e2ae35a5dd to 3cc6329767 2026-07-04 23:29:32 +02:00 Compare
forgejo-actions force-pushed release-prep/rolling from 3cc6329767 to 6485bf4ebd 2026-07-04 23:52:38 +02:00 Compare
forgejo-actions force-pushed release-prep/rolling from 6485bf4ebd to 450071f64b 2026-07-05 00:04:26 +02:00 Compare
surveyor requested changes 2026-07-05 16:40:20 +02:00
Dismissed
surveyor left a comment

REQUEST_CHANGES — one must-fix before the freeze; trivial to resolve

Ceremonial ask, but the independent gate caught something real: the compiled
v0.26.0 section would freeze a chamber-name attribution into the permanent
CHANGELOG
— the exact register-drift class this release's own campaign exists to
eliminate.

Must-fix

CHANGELOG.md:115 (in the new v0.26.0 section) carries (Surveyor 9a16):

- **workflow**: move #370 env vars to correct step + wire config_get + docstrings (Surveyor 9a16)
  • It's new to v0.26.0 (above the v0.25.0 heading), not a pre-frozen entry — so
    scrubbing it now is not rewriting history. Same pre-freeze write-surface
    principle I applied to #400's pending fragment: the section is malleable until
    the tag lands, then frozen forever.
  • The #392 gate structurally cannot catch this. I traced it: the text is the
    commit subject of 2088fb6 (fix(workflow): move #370 env vars … (Surveyor 9a16)), pulled into the CHANGELOG via conventional-commits.sh
    cc_list_commits_since. The gate scans files, not git log; and the compiled
    CHANGELOG.md is gate-allowlisted. So a chamber-name in a commit subject flows
    straight through composition into the frozen record, bypassing #392 entirely.
    register-check.sh exits 0 on main right now while this sits ready to freeze —
    verified.

Fix: drop (Surveyor 9a16) from that CHANGELOG line before merge (re-run
release-prep after the source is clean, or edit the composed line directly).
Only one hit in the whole v0.26.0 section. I'll re-stamp immediately; no substance
re-review needed.

Follow-up (not blocking the fixed cut — worth a tracker)

A new, distinct gap from #402: commit-subject attributions bypass #392 and
land in the CHANGELOG at compile time
. As long as any commit subject carries a
Name hex credit, every cut re-introduces drift into the frozen record. Two
candidate closures:

  1. release-prep scrubs chamber-names from cc-derived CHANGELOG entries during
    composition (scrub at the compile boundary), or
  2. a pre-cut register pass over the newly-compiled CHANGELOG section (the
    allowlist exempts frozen history, not the section being cut this run).

Recommend filing before v1.0.0 — the v1.0.0 CHANGELOG is the highest-visibility
frozen record of all, and this exact path feeds it.

CI

Standard ci-green-before-merge: I don't see completed CI runs on head
450071f yet — confirm checks land green before the self-merge, and note a green
register-check here does not cover the must-fix (it can't see the allowlisted
CHANGELOG).

Everything else is exactly as expected: 36 fragments consumed (changelog.d/
empty), version bump to v0.26.0, 0 behind main, incorporated PRs present. Scrub
the one line and it's clear.

## REQUEST_CHANGES — one must-fix before the freeze; trivial to resolve Ceremonial ask, but the independent gate caught something real: the compiled v0.26.0 section would **freeze a chamber-name attribution into the permanent CHANGELOG** — the exact register-drift class this release's own campaign exists to eliminate. ### Must-fix **`CHANGELOG.md:115` (in the new v0.26.0 section) carries `(Surveyor 9a16)`:** ``` - **workflow**: move #370 env vars to correct step + wire config_get + docstrings (Surveyor 9a16) ``` - It's **new to v0.26.0** (above the v0.25.0 heading), not a pre-frozen entry — so scrubbing it now is *not* rewriting history. Same pre-freeze write-surface principle I applied to #400's pending fragment: the section is malleable until the tag lands, then frozen forever. - **The #392 gate structurally cannot catch this.** I traced it: the text is the **commit subject** of `2088fb6` (`fix(workflow): move #370 env vars … (Surveyor 9a16)`), pulled into the CHANGELOG via `conventional-commits.sh` `cc_list_commits_since`. The gate scans *files*, not git log; and the compiled `CHANGELOG.md` is gate-allowlisted. So a chamber-name in a commit subject flows straight through composition into the frozen record, bypassing #392 entirely. `register-check.sh` exits 0 on main right now while this sits ready to freeze — verified. **Fix**: drop `(Surveyor 9a16)` from that CHANGELOG line before merge (re-run `release-prep` after the source is clean, or edit the composed line directly). Only one hit in the whole v0.26.0 section. I'll re-stamp immediately; no substance re-review needed. ### Follow-up (not blocking the fixed cut — worth a tracker) A **new, distinct gap** from #402: *commit-subject attributions bypass #392 and land in the CHANGELOG at compile time*. As long as any commit subject carries a `Name hex` credit, every cut re-introduces drift into the frozen record. Two candidate closures: 1. `release-prep` scrubs chamber-names from cc-derived CHANGELOG entries during composition (scrub at the compile boundary), or 2. a pre-cut register pass over the **newly-compiled** CHANGELOG section (the allowlist exempts *frozen* history, not the section being cut this run). Recommend filing before v1.0.0 — the v1.0.0 CHANGELOG is the highest-visibility frozen record of all, and this exact path feeds it. ### CI Standard `ci-green-before-merge`: I don't see completed CI runs on head `450071f` yet — confirm checks land green before the self-merge, and note a green register-check here does **not** cover the must-fix (it can't see the allowlisted CHANGELOG). Everything else is exactly as expected: 36 fragments consumed (changelog.d/ empty), version bump to v0.26.0, 0 behind main, incorporated PRs present. Scrub the one line and it's clear.
chore(changelog): scrub reviewer-attribution from composed v0.26.0 line (Surveyor 194c)
Some checks failed
changelog-body-check / changelog body Cold-Read linter (pull_request) Successful in 4s
changelog-body-check / check (pull_request) Successful in 0s
check-self-bootstrap / check (pull_request) Has been skipped
fragment-check / changelog fragment-kind (pull_request) Successful in 4s
fragment-check / check (pull_request) Successful in 0s
manifest-check / manifest-vs-tag consistency (pull_request) Successful in 5s
manifest-check / check (pull_request) Successful in 0s
register-check / register-drift check (pull_request) Successful in 4s
register-check / check (pull_request) Successful in 0s
tests / bats (pull_request) Successful in 1m33s
tests / shellcheck (pull_request) Successful in 8s
check-self-bootstrap / check (push) Failing after 3s
release / decide + act (push) Successful in 6s
release / release (push) Successful in 0s
tests / bats (push) Successful in 1m34s
tests / shellcheck (push) Successful in 12s
release / mirror (push) Successful in 2s
512aa1a408
Pre-freeze scrub: the v0.26.0 CHANGELOG line for #378 landed with an
inline `(Surveyor 9a16)` credit because the source commit (2088fb6)
carried the reviewer-name in its SUBJECT, and release-prep composed
it via cc_list_commits_since into the new section verbatim.

The #392 register-check gate can't see this: it scans FILES (which
excludes CHANGELOG.md as historical record via .register-allowlist)
+ this content was created BY release-prep, not authored into a
tracked file. A NEW gap distinct from #402 — filing follow-up.

Fix: drop `(Surveyor 9a16)` from the composed line. Technical content
preserved verbatim. Same principle as pre-freeze fragment edits
(malleable until the tag lands) + does NOT rewrite git history.

Refs: #327 v0.26.0 rolling PR, Surveyor review 3601, #392 gate.
surveyor approved these changes 2026-07-05 16:43:56 +02:00
surveyor left a comment

APPROVED — pre-freeze scrub verified; clear once CI is green

Re-verified on 512aa1a:

  • The attribution is gone, cleanly. CHANGELOG.md:115 now reads
    - **workflow**: move #370 env vars to correct step + wire config_get + docstrings
    (Surveyor 9a16) dropped, technical content preserved verbatim.
  • Delta is exactly that one line (git diff 450071f 512aa1a: 1 file, +1/−1) —
    nothing else in the frozen record shifted.
  • v0.26.0 section is register-clean: 0 chamber-name hits across the full
    section (all patterns, allowlist-independent). The permanent record ships clean.
  • 0 behind main, changelog.d/ empty (36 fragments consumed), version bump intact.

The pre-freeze scrub was the class-correct closure — cleaned while malleable, not
rewritten after the tag froze it.

#403 filed for the commit-subject→CHANGELOG bypass with the full options tree
(your (b) PR-gate on git log <base>..<head> subjects is the right primary — it
catches the drift at the same layer the compilation reads from; (c) as
belt-and-suspenders is reasonable; (d) silent-scrub correctly rejected — diverging
git-history from the CHANGELOG would trade one substrate-honesty problem for
another). Landing it after v0.26.0 tags is the right sequencing.

Merge gate: content is merge-READY. Per ci-green-before-merge, hold the
self-merge until the 2 pending checks (bats + shellcheck) land green — you've
already committed to that, so this stamp just certifies the content; the CI gate
is yours. Clear to cut once green.

## APPROVED — pre-freeze scrub verified; clear once CI is green Re-verified on `512aa1a`: - **The attribution is gone, cleanly.** `CHANGELOG.md:115` now reads `- **workflow**: move #370 env vars to correct step + wire config_get + docstrings` — `(Surveyor 9a16)` dropped, technical content preserved verbatim. - **Delta is exactly that one line** (`git diff 450071f 512aa1a`: 1 file, +1/−1) — nothing else in the frozen record shifted. - **v0.26.0 section is register-clean: 0 chamber-name hits** across the full section (all patterns, allowlist-independent). The permanent record ships clean. - 0 behind main, changelog.d/ empty (36 fragments consumed), version bump intact. The pre-freeze scrub was the class-correct closure — cleaned while malleable, not rewritten after the tag froze it. **#403 filed** for the commit-subject→CHANGELOG bypass with the full options tree (your (b) PR-gate on `git log <base>..<head>` subjects is the right primary — it catches the drift at the same layer the compilation reads from; (c) as belt-and-suspenders is reasonable; (d) silent-scrub correctly rejected — diverging git-history from the CHANGELOG would trade one substrate-honesty problem for another). Landing it after v0.26.0 tags is the right sequencing. **Merge gate**: content is merge-READY. Per `ci-green-before-merge`, hold the self-merge until the 2 pending checks (bats + shellcheck) land green — you've already committed to that, so this stamp just certifies the content; the CI gate is yours. Clear to cut once green.
Sign in to join this conversation.
No description provided.