From 834f97e46d0f0e2d296d844ce7ccad0a1d67af1a Mon Sep 17 00:00:00 2001 From: Michael Tarassov Date: Sun, 23 Aug 2026 14:38:26 +0500 Subject: [PATCH] =?UTF-8?q?feat(release):=20changelog=20fragments=20?= =?UTF-8?q?=E2=80=94=20parallel-safe=20changelog=20authoring?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit CHANGELOG.md's [Unreleased] section is the most-conflicted file in parallel development (touched by a third of integrations in the blast-radius review). A PR now drops changelog.d/..md — one markdown paragraph, no leading '- ' — and two PRs never touch the same line again. - scripts/assemble-changelog.sh folds fragments into [Unreleased] (creates ### sections in Keep-a-Changelog order, deletes consumed fragments); --check validates fragment format only and runs in the quick CI gate job - scripts/release.sh now runs the assemble itself and retitles [Unreleased] to '## [] — date' with a fresh empty [Unreleased] on top: the one manual step left in a release is gone (a hand-retitled heading and direct [Unreleased] edits stay legal — fragments are preferred, not required) - gate selftest: run_case learns gate arguments; new case renames the tracked changelog.d/README.md into a bad-typed fragment and requires --check to name it — 20 planted breakages across nine gate scripts - docs: CONTRIBUTING Changelog+Release sections, changelog.d/README.md, INDEX, CLAUDE.md --- .github/workflows/ci.yml | 6 +- .github/workflows/gates-nightly.yml | 2 +- CLAUDE.md | 22 ++- CONTRIBUTING.md | 45 ++++-- changelog.d/README.md | 34 ++++ changelog.d/changelog-fragments.added.md | 7 + docs/INDEX.md | 7 +- scripts/assemble-changelog.sh | 191 +++++++++++++++++++++++ scripts/check-selftest.sh | 48 +++++- scripts/release.sh | 68 +++++--- 10 files changed, 381 insertions(+), 49 deletions(-) create mode 100644 changelog.d/README.md create mode 100644 changelog.d/changelog-fragments.added.md create mode 100755 scripts/assemble-changelog.sh diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 0f98429..80c187f 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -201,10 +201,12 @@ jobs: run: ./scripts/check-module-deps.sh - name: Check config key sync (src reads vs config jsons, CONFIG.md, helm env lists) run: ./scripts/check-config-sync.sh + - name: Check changelog fragment format (changelog.d/ — fragments stay optional) + run: ./scripts/assemble-changelog.sh --check gate-selftest: runs-on: ubuntu-latest - # Proves the eight check-* gates BITE: per gate, copy the subtree it reads, + # Proves the nine gate scripts BITE: per gate, copy the subtree it reads, # plant a known breakage (counted mutators; a no-op mutation is itself a # failure), and require the gate to fail AND name what it found — after # first PASSING the untouched copy as a control. A gate only ever run @@ -260,7 +262,7 @@ jobs: curl -fsSL https://raw.githubusercontent.com/helm/helm/main/scripts/get-helm-3 | bash sudo curl -fsSL -o /usr/local/bin/yq https://github.com/mikefarah/yq/releases/download/v4.44.6/yq_linux_amd64 sudo chmod +x /usr/local/bin/yq - - name: Gate selftest (18 planted breakages must be caught) + - name: Gate selftest (20 planted breakages must be caught) if: steps.scope.outputs.relevant == 'true' run: ./scripts/check-selftest.sh diff --git a/.github/workflows/gates-nightly.yml b/.github/workflows/gates-nightly.yml index 3751438..a55010c 100644 --- a/.github/workflows/gates-nightly.yml +++ b/.github/workflows/gates-nightly.yml @@ -28,7 +28,7 @@ jobs: curl -fsSL https://raw.githubusercontent.com/helm/helm/main/scripts/get-helm-3 | bash sudo curl -fsSL -o /usr/local/bin/yq https://github.com/mikefarah/yq/releases/download/v4.44.6/yq_linux_amd64 sudo chmod +x /usr/local/bin/yq - - name: Gate selftest (18 planted breakages must be caught) + - name: Gate selftest (20 planted breakages must be caught) run: ./scripts/check-selftest.sh - name: Module dependency DAG gate run: ./scripts/check-module-deps.sh diff --git a/CLAUDE.md b/CLAUDE.md index 6ab6ebc..24558aa 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -20,9 +20,17 @@ map of all documentation; `docs/CONVENTIONS.md` is the pattern reference. wiring): `./scripts/new-module.sh ` - Migration: `make new-migration SLUG=` - React page: `./scripts/new-react-page.sh` -- Release: `./scripts/release.sh ` — after the human retitles the - CHANGELOG `[Unreleased]` heading, bumps every other version point in one - command (CMakeLists + 9 helm tag pins + 4 Chart.yaml appVersions + +- Changelog entry: drop a fragment `changelog.d/..md` (type ∈ + added|changed|fixed|removed|security; bullet text WITHOUT the leading + `- `, format: `changelog.d/README.md`) — parallel-safe, two PRs never + conflict; editing `[Unreleased]` directly stays legal. + `./scripts/assemble-changelog.sh --check` gates fragment FORMAT in CI + (fragments themselves are optional). +- Release: `./scripts/release.sh ` — folds `changelog.d/` fragments + into `[Unreleased]` (assemble-changelog.sh), retitles it to + `## [] — date` with a fresh empty `[Unreleased]` on top (a heading + retitled by hand is accepted too), then bumps every other version point + in one command (CMakeLists + 9 helm tag pins + 4 Chart.yaml appVersions + `.template-version`); commits nothing (CONTRIBUTING.md "Release") - Fork→template sync: `./scripts/sync-upstream.sh` — run IN a fork; pulls template fixes by three-way patching between release tarballs (base = @@ -98,9 +106,11 @@ gates by construction. Hand-rolled versions usually don't. 2. `./scripts/check-openapi-drift.sh && ./scripts/check-routes-registered.sh && ./scripts/check-test-buckets.sh && ./scripts/check-version-sync.sh && ./scripts/check-frontend-nginx-sync.sh && ./scripts/check-module-deps.sh - && ./scripts/check-config-sync.sh` — seconds, no build. - Touched a `check-*` script? Also run `./scripts/check-selftest.sh` — - plants 19 breakages and requires every gate to catch and name them + && ./scripts/check-config-sync.sh && ./scripts/assemble-changelog.sh --check` + — seconds, no build. + Touched a `check-*` script (or assemble-changelog.sh)? Also run + `./scripts/check-selftest.sh` — + plants 20 breakages and requires every gate to catch and name them (needs helm+yq; in CI `gate-selftest` self-scopes to diffs touching `scripts/`, `helm/` or `.github/workflows/`, with a nightly unconditional backstop in `.github/workflows/gates-nightly.yml`) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 6174015..654d7d6 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -55,6 +55,28 @@ Closes #123 Types: `feat`, `fix`, `refactor`, `perf`, `docs`, `test`, `build`, `ci`, `chore`. Scope is optional but recommended (e.g. `feat(auth):`, `fix(jobs):`). +## Changelog + +`CHANGELOG.md` follows Keep a Changelog. Two ways to record a change — the +fragment is preferred, because it cannot conflict: + +- **Fragment (preferred):** add one file `changelog.d/..md`, + where `` is your branch name or a short slug and `` is one of + `added` / `changed` / `fixed` / `removed` / `security`. The file holds the + bullet text as ONE markdown paragraph, WITHOUT the leading `- ` (the + assembler adds it). Two parallel PRs then touch two different files + instead of the same `[Unreleased]` lines — the changelog stops being the + most-conflicted file in every integration. Format details: + `changelog.d/README.md`. CI validates fragment *format* + (`./scripts/assemble-changelog.sh --check`) but never *requires* a + fragment — PRs with nothing changelog-worthy stay clean. +- **Direct edit (legal):** append under `## [Unreleased]` in `CHANGELOG.md` + as before. Fine for cross-cutting PRs; expect merge conflicts there. + +Fragments are folded into `[Unreleased]` (and deleted) at release time by +`scripts/release.sh`, or any time by a manual +`./scripts/assemble-changelog.sh`. + ## Code style - C++20. Header-only modules under `src/`, single `main.cpp` per binary. @@ -112,18 +134,21 @@ Semver, tagged on `master`. The release version lives in more places than one edit can reach by hand (that is how three releases shipped with a stale `project(VERSION …)`), so use the script: -1. In `CHANGELOG.md`, retitle `## [Unreleased]` to `## [] — YYYY-MM-DD` - and add a fresh empty `## [Unreleased]` above it. The content stays - human-written. -2. `./scripts/release.sh ` — validates semver + monotonicity, then bumps - every remaining version point in one command: `CMakeLists.txt - project(VERSION …)`, the 9 image-tag pins in +1. `./scripts/release.sh ` — validates semver + monotonicity, then: + folds any `changelog.d/` fragments into `## [Unreleased]` + (`assemble-changelog.sh`, deleting the consumed fragments), retitles that + section to `## [] — YYYY-MM-DD` keeping a fresh empty + `## [Unreleased]` on top (a heading you already retitled by hand is + accepted too; an empty `[Unreleased]` with no fragments aborts — write + the notes first), and bumps every remaining version point in one command: + `CMakeLists.txt project(VERSION …)`, the 9 image-tag pins in `helm/cpp-env/values{,-demo,-stage}.yaml`, and the 4 `Chart.yaml` `appVersion` fields. It re-runs `scripts/check-version-sync.sh` (which - gates all of those in CI) and prints the diff. It commits nothing. -3. Commit changelog + bumps as ONE commit: - `chore(release): `. -4. After it lands on `master`, `git tag v && git push origin v`. + gates all of those in CI) and prints the diff. It commits nothing; the + changelog *content* stays human-written — the script only moves it. +2. Review `git diff`, then commit changelog + fragment deletions + bumps as + ONE commit: `chore(release): `. +3. After it lands on `master`, `git tag v && git push origin v`. A tag matching `v*.*.*` triggers `.github/workflows/release.yml`, which builds the app, worker and frontend images, pushes them to `ghcr.io//` (and `-worker` / `-frontend`) with the diff --git a/changelog.d/README.md b/changelog.d/README.md new file mode 100644 index 0000000..739569c --- /dev/null +++ b/changelog.d/README.md @@ -0,0 +1,34 @@ +# changelog.d — changelog fragments + +`CHANGELOG.md`'s `[Unreleased]` section is the most conflict-prone file in +the repo: every PR appends to the same few lines. Instead of editing it, +drop ONE file per change here: + + ..md + +- `` — your branch name or a short slug (`fix-login-429`, + `orgs-kit`). It only keeps file names unique across parallel PRs; it + never appears in the changelog. +- `` — one of `added`, `changed`, `fixed`, `removed`, `security`: + the Keep-a-Changelog section the bullet lands in. + +The file content is the bullet text: ONE markdown paragraph, WITHOUT the +leading `- ` — the assembler adds the prefix and indents continuation +lines. Backticks, links and inline markdown are fine. + +Example — `changelog.d/wallet-topup.added.md`: + + PayPal checkout top-ups credited to the append-only wallet ledger + (`src/billing/Wallet.hpp`), idempotent under webhook redelivery. + +At release time `scripts/release.sh` (or a manual +`./scripts/assemble-changelog.sh`) folds every fragment into +`## [Unreleased]` — creating the `### Added` / `### Changed` / … +subsections in Keep-a-Changelog order when missing — and deletes the +consumed files; the deletions are committed with the release. + +`./scripts/assemble-changelog.sh --check` validates fragment format +without touching anything; CI runs it in the quick gate job. Fragments are +OPTIONAL: editing `[Unreleased]` directly remains legal (a fragment is +preferred because it cannot conflict). This README is ignored by the +assembler. diff --git a/changelog.d/changelog-fragments.added.md b/changelog.d/changelog-fragments.added.md new file mode 100644 index 0000000..3b1e8d9 --- /dev/null +++ b/changelog.d/changelog-fragments.added.md @@ -0,0 +1,7 @@ +Changelog fragments: a PR now records its changelog entry as its own file +`changelog.d/..md` (type ∈ added|changed|fixed|removed|security, +format: `changelog.d/README.md`) instead of competing for the same +`[Unreleased]` lines — `scripts/release.sh` folds the fragments in and +retitles `[Unreleased]` itself, `./scripts/assemble-changelog.sh --check` +gates fragment format in CI (fragments stay optional, direct `[Unreleased]` +edits remain legal), and the gate selftest grows to 20 planted breakages. diff --git a/docs/INDEX.md b/docs/INDEX.md index 2ff9116..20f8672 100644 --- a/docs/INDEX.md +++ b/docs/INDEX.md @@ -11,7 +11,7 @@ question instead of grepping the tree. | [`../README.md`](../README.md) | Getting started, what's in the box, quickstart, repo layout | | [`../CONTRIBUTING.md`](../CONTRIBUTING.md) | Pre-commit setup, dev workflow, commit-message convention, release flow | | [`../SECURITY.md`](../SECURITY.md) | Disclosure policy + production-hardening checklist | -| [`../CHANGELOG.md`](../CHANGELOG.md) | Versioned change log (semver, edited under `## [Unreleased]`) | +| [`../CHANGELOG.md`](../CHANGELOG.md) | Versioned change log (semver; authored via `changelog.d/` fragments — parallel-safe, see [`../changelog.d/README.md`](../changelog.d/README.md) — or directly under `## [Unreleased]`) | | [`../REMOVING-THE-DEMO.md`](../REMOVING-THE-DEMO.md) | What's reference-only (flask-base) vs the real app, and how to strip it (`init-project.sh --no-demo`) | ## Worked examples & deep-dives @@ -104,7 +104,8 @@ question instead of grepping the tree. | `check-routes-registered.sh` | Verify every controller ADD_METHOD_TO route is in `Api::get_endpoints()` (symmetric to the OpenAPI drift check) | | `check-test-buckets.sh` | Verify test suites sit in the right bucket — classified by DIRECTORY, fails on a suite-name clash across unit/integration | | `check-version-sync.sh` | Verify `project(VERSION …)` in CMakeLists.txt, the 9 helm image-tag pins (`helm/cpp-env/values{,-demo,-stage}.yaml`) and the 4 `Chart.yaml` `appVersion` fields all match the newest released CHANGELOG heading (`[Unreleased]` on top stays green) | -| `release.sh` | Bump every version point of a release in one command (CMakeLists + 9 helm tag pins + 4 appVersions) after the human retitles the CHANGELOG heading; verifies with check-version-sync.sh, commits nothing | +| `release.sh` | One-command release: folds `changelog.d/` fragments into `[Unreleased]`, retitles it to `## [] — date` (accepts a hand-retitled heading too), bumps every version point (CMakeLists + 9 helm tag pins + 4 appVersions); verifies with check-version-sync.sh, commits nothing | +| `assemble-changelog.sh` | Fold `changelog.d/..md` fragments into CHANGELOG's `[Unreleased]` (creates `###` sections in Keep-a-Changelog order, deletes consumed fragments); `--check` validates fragment format only — CI runs it, fragments themselves stay optional | | `check-frontend-nginx-sync.sh` | Verify `frontend/nginx.conf` and the helm cpp-frontend ConfigMap agree on the proxied backend routes | | `check-module-deps.sh` | Verify every cross-directory `#include` in `src/` is an edge declared in `docs/module-deps.txt`; hard-forbids `utils -> *`, non-entry-point includes of `core/Core.hpp`, and `webhooks -> email` | | `check-config-sync.sh` | Verify every config read in `src/` (`cfg.get("path", "ENV", …)` + getenv) exists in `config/config.json` AND `config/config.sample.json` and is documented in `docs/CONFIG.md`; flags stale doc rows and stale helm env lines (exceptions: `docs/config-sync-allowlist.txt`) | @@ -112,7 +113,7 @@ question instead of grepping the tree. | `check-artifact.py` | Content + leaked-template-syntax gate over ONE rendered artifact's extracted text (opt-in, for document-rendering forks — see docs/RENDER-GATE.md) | | `render-artifacts.sh` | Loop for the opt-in artifact gate: render every `templates/render/*/fixtures/*.json` via `RENDER_CMD`/`EXTRACT_CMD`, then gate with `check-artifact.py`; zero fixtures = failure | | `check-artifact-selftest.sh` | Mandatory selftest for the artifact gate: healthy example (incl. hostile data fixture) must PASS, two counted mutations must be caught and named | -| `check-selftest.sh` | Prove all eight `check-*` gates bite: plant 18 known breakages in scratch copies, require each gate to fail AND name it (control-pass first, counted mutators, a no-op mutation is itself a failure; needs helm+yq) | +| `check-selftest.sh` | Prove all nine gate scripts bite (the eight `check-*` gates + `assemble-changelog.sh --check`): plant 20 known breakages in scratch copies, require each gate to fail AND name it (control-pass first, counted mutators, a no-op mutation is itself a failure; needs helm+yq) | | `prod-check.sh` | Pre-deploy assertions on a production config (auth, secrets, TLS, fail-closed) | | `lint-openapi.sh` | Spectral lint with project ruleset | | `make-jwt.sh` | Mint a dev HS256 JWT (no Python/Node deps) | diff --git a/scripts/assemble-changelog.sh b/scripts/assemble-changelog.sh new file mode 100755 index 0000000..96fa78c --- /dev/null +++ b/scripts/assemble-changelog.sh @@ -0,0 +1,191 @@ +#!/usr/bin/env bash +# assemble-changelog.sh [--check] — fold changelog.d/ fragments into CHANGELOG.md. +# +# CHANGELOG.md's [Unreleased] section is the single most conflict-prone file +# in this repo (touched by a third of integrations in the blast-radius +# review): every PR appends to the same few lines. Fragments dodge the +# conflict — a PR adds its own file instead: +# +# changelog.d/..md type ∈ added|changed|fixed|removed|security +# +# holding ONE markdown paragraph: the bullet text WITHOUT the leading "- " +# (the assembler adds the prefix and indents continuation lines). Two +# parallel PRs then touch two different files instead of the same lines. +# changelog.d/README.md documents the format and is ignored here. +# +# Default mode: validate every fragment, fold the bullets into CHANGELOG.md's +# '## [Unreleased]' section (### subsections created in Keep-a-Changelog +# order when missing), DELETE the consumed fragments, print what happened. +# scripts/release.sh runs this automatically before retitling [Unreleased]. +# +# --check: validate only (name, type, non-emptiness, single paragraph), +# touch nothing. CI runs this in the quick gate job. Fragments themselves +# stay OPTIONAL — a PR may still edit [Unreleased] directly; this gate only +# rejects fragments that exist but could not be assembled. +# +# Usage: ./scripts/assemble-changelog.sh [--check] +set -euo pipefail + +SCRIPT_DIR="$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd)" +REPO="${REPO_ROOT:-$(cd -- "$SCRIPT_DIR/.." && pwd)}" + +mode=assemble +case "${1:-}" in +"") ;; +--check) mode=check ;; +*) + echo "usage: ./scripts/assemble-changelog.sh [--check]" >&2 + exit 2 + ;; +esac + +command -v python3 >/dev/null 2>&1 || { + echo "assemble-changelog: python3 required" >&2 + exit 1 +} + +python3 - "$REPO" "$mode" <<'PY' +import os, re, sys + +repo, mode = sys.argv[1], sys.argv[2] +frag_dir = os.path.join(repo, "changelog.d") +changelog = os.path.join(repo, "CHANGELOG.md") + +VALID = ["added", "changed", "fixed", "removed", "security"] +# Keep-a-Changelog section order (the subset the fragment types cover). +CANON = ["Added", "Changed", "Removed", "Fixed", "Security"] + +if not os.path.isdir(frag_dir): + print("assemble-changelog: no changelog.d/ directory — nothing to do") + sys.exit(0) + +# --- collect + validate ------------------------------------------------------ +errors = [] +frags = [] # (path, section title, content lines) +for base in sorted(os.listdir(frag_dir)): + path = os.path.join(frag_dir, base) + rel = "changelog.d/" + base + if not os.path.isfile(path) or base == "README.md": + continue # the format doc is not a fragment + if base.startswith("."): + errors.append((rel, "hidden files do not belong here")) + continue + if not base.endswith(".md"): + errors.append((rel, "not a .md file — fragments are ..md")) + continue + topic, dot, ftype = base[:-3].rpartition(".") + if not dot or not topic or not ftype: + errors.append((rel, "name must be ..md (e.g. fix-login.fixed.md)")) + continue + if ftype not in VALID: + errors.append((rel, "unknown type '%s' (valid: added|changed|fixed|removed|security)" + % ftype)) + continue + with open(path, encoding="utf-8") as fh: + lines = [ln.rstrip() for ln in fh.read().strip().splitlines()] + if not lines: + errors.append((rel, "empty fragment — write the bullet text (without the leading '- ')")) + continue + if lines[0].startswith(("- ", "* ")): + errors.append((rel, "starts with a bullet prefix — the assembler adds '- ' itself")) + continue + if any(not ln for ln in lines): + errors.append((rel, "must be a single paragraph (no blank lines inside the text)")) + continue + frags.append((path, ftype.capitalize(), lines)) + +if errors: + for rel, why in errors: + print("assemble-changelog: %s: %s" % (rel, why), file=sys.stderr) + print("assemble-changelog: %d broken fragment(s) — fix the file name/content" + % len(errors), file=sys.stderr) + print(" (format reference: changelog.d/README.md)", file=sys.stderr) + sys.exit(1) + +if not frags: + print("assemble-changelog: no fragments in changelog.d/ — nothing to %s" + % ("check" if mode == "check" else "assemble")) + sys.exit(0) + +if mode == "check": + print("assemble-changelog: %d fragment(s) in changelog.d/, format OK" % len(frags)) + sys.exit(0) + +# --- fold into CHANGELOG.md's [Unreleased] section --------------------------- +with open(changelog, encoding="utf-8") as fh: + lines = fh.read().splitlines() + +# Find '## [Unreleased]', creating it above the newest release when missing. +unrel = [i for i, ln in enumerate(lines) if ln.strip() == "## [Unreleased]"] +if len(unrel) > 1: + sys.exit("assemble-changelog: CHANGELOG.md has %d '## [Unreleased]' headings — " + "expected at most 1" % len(unrel)) +if unrel: + u = unrel[0] +else: + u = next((i for i, ln in enumerate(lines) if ln.startswith("## [")), len(lines)) + lines[u:u] = ["## [Unreleased]", ""] + +# The Unreleased block runs until the next '## ' heading (or EOF). +end = next((i for i in range(u + 1, len(lines)) if lines[i].startswith("## ")), len(lines)) +block = lines[u + 1:end] + +# Split the block into a preamble and its '### ' subsections. +pre, sections, cur = [], [], None +for ln in block: + m = re.match(r"^###\s+(\S.*?)\s*$", ln) + if m: + cur = [m.group(1), []] + sections.append(cur) + elif cur is None: + pre.append(ln) + else: + cur[1].append(ln) + +def strip_blank(body): + while body and not body[0].strip(): + body.pop(0) + while body and not body[-1].strip(): + body.pop() + return body + +for _, body in sections: + strip_blank(body) +strip_blank(pre) + +# Append bullets to existing subsections; create missing ones (at the end of +# the block, in canonical order among themselves). +by_title = {title: body for title, body in sections} +counts = {} +for title in CANON: + bullets = [ln for path, t, content in frags if t == title + for ln in ["- " + content[0]] + [" " + c for c in content[1:]]] + if not bullets: + continue + counts[title] = sum(1 for _, t, _ in frags if t == title) + if title in by_title: + by_title[title].extend(bullets) + else: + sections.append([title, bullets]) + +new_block = [] +if pre: + new_block += [""] + pre +for title, body in sections: + new_block += ["", "### " + title] + body +if end < len(lines): + new_block.append("") # one blank line before the next release heading +lines[u + 1:end] = new_block + +with open(changelog, "w", encoding="utf-8") as fh: + fh.write("\n".join(lines) + "\n") + +for path, _, _ in frags: + os.unlink(path) + +print("assemble-changelog: folded %d fragment(s) into CHANGELOG.md [Unreleased]:" % len(frags)) +for title in CANON: + if title in counts: + print(" %-9s %d bullet(s)" % (title, counts[title])) +print(" fragments deleted — commit CHANGELOG.md together with the deletions") +PY diff --git a/scripts/check-selftest.sh b/scripts/check-selftest.sh index ab5e29c..b76e57e 100755 --- a/scripts/check-selftest.sh +++ b/scripts/check-selftest.sh @@ -6,12 +6,13 @@ # `exit 0`, and this template's own history has both failure modes on record: # check-test-buckets.sh read only its first directory argument (tests/api was # never scanned), and a downstream fork shipped a gitleaks config that scanned -# with zero rules — green for months, checking nothing. So: for each of the eight -# check scripts, copy the subtree it reads into a scratch dir, plant a known +# with zero rules — green for months, checking nothing. So: for each of the nine +# gate scripts, copy the subtree it reads into a scratch dir, plant a known # breakage, and require the gate to FAIL and to NAME what it found. Two planted # breakages per gate (five for check-version-sync.sh, which also guards the -# helm image pins, Chart.yaml appVersions and the .template-version stamp), -# nineteen in all. +# helm image pins, Chart.yaml appVersions and the .template-version stamp; one +# for assemble-changelog.sh --check, the changelog-fragment format gate), +# twenty in all. # # Every case follows the same discipline (borrowed from the cyber-accountant # fork's check-render-selftest.sh, which learned it the expensive way): @@ -104,7 +105,8 @@ digest_of() { # run_case