feat(register-check): commit-subject → CHANGELOG bypass gate (#403) #434

Merged
quartermaster merged 2 commits from i/403-commit-subject-register-gate into main 2026-07-06 12:27:41 +02:00

Summary

Closes #403 — adds two new modes to scripts/register-check.sh + a belt-and-suspenders scrub call in scripts/release-prep.sh, closing the class-completeness gap where a chamber-name attribution in a git commit SUBJECT rides through cc_list_commits_since → CHANGELOG.md past the existing file-scan gate. Empirical anchor: v0.26.0 PR#327 subject 2088fb69 carrying "Surveyor 9a16" caught manually pre-freeze.

Substrate delta

(b) PR-time commit-subject scan

  • scripts/register-check.sh --git-log RANGE: scans commit subjects in git log --format='%h\t%s' RANGE. Same regex + pattern list as file-scan (single source of truth). Fires fail-loud with actionable "rewrite subject via git rebase -i" message.
  • .forgejo/workflows/reusable-register-check.yml: new opt-in input check_commit_subjects: 'false' (default OFF for backwards-compat). When 'true' + event is pull_request, adds a second step running --git-log ${base.sha}..${head.sha}. Fetch-depth raised to 0 so the range is walkable.
  • .forgejo/workflows/register-check.yml (toolkit-self): opts into check_commit_subjects: 'true'. Toolkit uses cc_list_commits_since; adopters using fragments-only paths don't need the subject-scan and stay on the default.

(c) release-prep post-composition scrub

  • scripts/register-check.sh --stdin: scans arbitrary text from stdin. Same pattern list.
  • scripts/release-prep.sh: adds a scrub call immediately after changelog_normalize_paragraphs, BEFORE changelog_transition writes to CHANGELOG.md. Belt-and-suspenders against (b): even if the PR-gate is disabled by an adopter, or a commit was pushed directly to main bypassing the PR, release-prep won't ship drift.

Substrate discipline

  • Single source of truth: all three surfaces (--file-scan / --git-log / --stdin) share the same REGISTER_PATTERNS array. Extending the pattern list extends all three simultaneously — no drift by construction.
  • Default OFF for the PR-gate: adopters using fragments-only paths don't pay for a scan they don't need. Toolkit-self opts in via its own consumer wrapper.
  • release-prep scrub is unconditional: every adopter benefits from the compile-boundary defense even without opting into the PR-gate.
  • Same class as #417 range-walk: defense-in-depth at complementary layers (contributor-side / compile-boundary). Consistent with the toolkit's substrate-of-record discipline.

Verification

  • tests/register-check.bats: 6 new tests (17 total). Lock:
    • --git-log FAILS on chamber-name subject (empirical 2088fb69 shape)
    • --git-log PASSES on clean subjects
    • --git-log without RANGE arg exits 2 (usage error)
    • --stdin FAILS on chamber-name text (composed-CHANGELOG-line shape)
    • --stdin PASSES on clean text
    • --stdin case-insensitive (lowercase chamber-name fires)
  • Full sweep: 697/697 EXIT=0 (was 691 pre-fix + 6 new tests, count-verified per feedback_bats_sweep_count_verification).
  • Register-check clean at HEAD (all three modes).
  • Shellcheck --severity=warning clean on register-check.sh + release-prep.sh.
  • Own-fragment dogfood: changelog.d/403.added.md passes all gates — the new check 7/8/9 density thresholds (from #421 Part 1), the file-scan register-check, AND the new --stdin register-check (fed by the release-prep scrub path). Dogfoods every gate this PR ships and every gate the prior sprint delta shipped.

Verification AC (from tracker)

  • Bats: PR-gate catches a chamber-name-in-commit-subject test fixture
  • Bats: release-prep aborts if composed CHANGELOG section carries drift (proven via --stdin fixtures; the release-prep integration path calls --stdin on the composed file)
  • Regression: 2088fb69-shape commit subjects fail the gate

Empirical status

Design-gap-of-record before v0.26.0's manual pre-freeze catch. Since #392 shipped, the file-scan has caught every subsequent leak in FILES; this closes the last known bypass path (SUBJECTS feeding the compose-step). Combined with the release-prep scrub, drift can no longer land in the released tag by any known path.

Expected-red enumeration (per refined feedback_ci_green_before_merge):

  • check-self-bootstrap (PR) — pin-drift semantic: I touched scripts/register-check.sh, scripts/release-prep.sh, .forgejo/workflows/reusable-register-check.yml (compose-scripts). Self-heals at next re-pin cut.
  • release / decide + act (push) — stuck-main state pending v0.27.0 recovery. Unchanged.

Files

  • Modified: scripts/register-check.sh (+~90 lines, --git-log + --stdin modes), scripts/release-prep.sh (+16 lines, scrub call), .forgejo/workflows/reusable-register-check.yml (+30 lines, opt-in input + step), .forgejo/workflows/register-check.yml (+8 lines, toolkit-self opts in), tests/register-check.bats (+6 tests + bats-min-version guard)
  • New: changelog.d/403.added.md

Refs: #403 (main tracker), Surveyor 3601 review on PR#327 (empirical 2088fb69 anchor), Bosun dispatch 7c26 (ratified pick), #392 (parent register-check class), #402 (sibling allowlist-scope gap), #396 (sibling line-level exemption gap).

## Summary **Closes #403** — adds two new modes to `scripts/register-check.sh` + a belt-and-suspenders scrub call in `scripts/release-prep.sh`, closing the class-completeness gap where a chamber-name attribution in a git commit SUBJECT rides through `cc_list_commits_since` → CHANGELOG.md past the existing file-scan gate. Empirical anchor: v0.26.0 PR#327 subject `2088fb69` carrying "Surveyor 9a16" caught manually pre-freeze. ## Substrate delta ### (b) PR-time commit-subject scan - **`scripts/register-check.sh --git-log RANGE`**: scans commit subjects in `git log --format='%h\t%s' RANGE`. Same regex + pattern list as file-scan (single source of truth). Fires fail-loud with actionable "rewrite subject via `git rebase -i`" message. - **`.forgejo/workflows/reusable-register-check.yml`**: new opt-in input `check_commit_subjects: 'false'` (default OFF for backwards-compat). When `'true'` + event is `pull_request`, adds a second step running `--git-log ${base.sha}..${head.sha}`. Fetch-depth raised to 0 so the range is walkable. - **`.forgejo/workflows/register-check.yml`** (toolkit-self): opts into `check_commit_subjects: 'true'`. Toolkit uses `cc_list_commits_since`; adopters using fragments-only paths don't need the subject-scan and stay on the default. ### (c) release-prep post-composition scrub - **`scripts/register-check.sh --stdin`**: scans arbitrary text from stdin. Same pattern list. - **`scripts/release-prep.sh`**: adds a scrub call immediately after `changelog_normalize_paragraphs`, BEFORE `changelog_transition` writes to CHANGELOG.md. Belt-and-suspenders against (b): even if the PR-gate is disabled by an adopter, or a commit was pushed directly to main bypassing the PR, release-prep won't ship drift. ## Substrate discipline - **Single source of truth**: all three surfaces (`--file-scan` / `--git-log` / `--stdin`) share the same `REGISTER_PATTERNS` array. Extending the pattern list extends all three simultaneously — no drift by construction. - **Default OFF for the PR-gate**: adopters using fragments-only paths don't pay for a scan they don't need. Toolkit-self opts in via its own consumer wrapper. - **release-prep scrub is unconditional**: every adopter benefits from the compile-boundary defense even without opting into the PR-gate. - **Same class as #417 range-walk**: defense-in-depth at complementary layers (contributor-side / compile-boundary). Consistent with the toolkit's substrate-of-record discipline. ## Verification - **`tests/register-check.bats`**: **6 new tests** (17 total). Lock: - `--git-log` FAILS on chamber-name subject (empirical `2088fb69` shape) - `--git-log` PASSES on clean subjects - `--git-log` without RANGE arg exits 2 (usage error) - `--stdin` FAILS on chamber-name text (composed-CHANGELOG-line shape) - `--stdin` PASSES on clean text - `--stdin` case-insensitive (lowercase chamber-name fires) - **Full sweep**: **697/697 EXIT=0** (was 691 pre-fix + 6 new tests, count-verified per `feedback_bats_sweep_count_verification`). - **Register-check clean at HEAD** (all three modes). - **Shellcheck --severity=warning clean** on `register-check.sh` + `release-prep.sh`. - **Own-fragment dogfood**: `changelog.d/403.added.md` passes **all** gates — the new check 7/8/9 density thresholds (from #421 Part 1), the file-scan register-check, AND the new `--stdin` register-check (fed by the release-prep scrub path). Dogfoods every gate this PR ships and every gate the prior sprint delta shipped. ## Verification AC (from tracker) - [x] Bats: PR-gate catches a chamber-name-in-commit-subject test fixture - [x] Bats: release-prep aborts if composed CHANGELOG section carries drift (proven via `--stdin` fixtures; the release-prep integration path calls `--stdin` on the composed file) - [x] Regression: 2088fb69-shape commit subjects fail the gate ## Empirical status Design-gap-of-record before v0.26.0's manual pre-freeze catch. Since #392 shipped, the file-scan has caught every subsequent leak in FILES; this closes the last known bypass path (SUBJECTS feeding the compose-step). Combined with the release-prep scrub, drift can no longer land in the released tag by any known path. Expected-red enumeration (per refined `feedback_ci_green_before_merge`): - `check-self-bootstrap (PR)` — pin-drift semantic: I touched `scripts/register-check.sh`, `scripts/release-prep.sh`, `.forgejo/workflows/reusable-register-check.yml` (compose-scripts). Self-heals at next re-pin cut. - `release / decide + act (push)` — stuck-main state pending v0.27.0 recovery. Unchanged. ## Files - **Modified**: `scripts/register-check.sh` (+~90 lines, --git-log + --stdin modes), `scripts/release-prep.sh` (+16 lines, scrub call), `.forgejo/workflows/reusable-register-check.yml` (+30 lines, opt-in input + step), `.forgejo/workflows/register-check.yml` (+8 lines, toolkit-self opts in), `tests/register-check.bats` (+6 tests + bats-min-version guard) - **New**: `changelog.d/403.added.md` Refs: #403 (main tracker), Surveyor 3601 review on PR#327 (empirical `2088fb69` anchor), Bosun dispatch 7c26 (ratified pick), #392 (parent register-check class), #402 (sibling allowlist-scope gap), #396 (sibling line-level exemption gap).
feat(register-check): commit-subject → CHANGELOG bypass gate + release-prep scrub (closes #403)
Some checks failed
check-self-bootstrap / check (pull_request) Failing after 4s
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 1m51s
tests / shellcheck (pull_request) Successful in 8s
a06cd67657
## Adds

Two new modes on scripts/register-check.sh + a release-prep.sh
belt-and-suspenders scrub, closing a class-completeness gap in the
#392 mechanized gate.

## Root cause

A chamber-name attribution in a git commit SUBJECT rides through
`cc_list_commits_since` → `cc_categorize_commits_since` → CHANGELOG.md
at release-prep time, bypassing the file-scan gate. The file-scan
gate scans CHANGELOG.md (as an allowlist historical record); the
BYPASS lives in the compose step that READS from subjects. Subjects
themselves are never scanned.

Empirical anchor: v0.26.0 PR#327 (round-4 sweep) commit 2088fb69
subject `fix(workflow): move #370 env vars to correct step + wire
config_get + docstrings (Surveyor 9a16)` was composed verbatim into
CHANGELOG.md at release-prep time. Caught manually at review time
(pre-freeze scrub, review 3601 must-fix). Without a gate, the
`Surveyor 9a16` attribution would have frozen into the permanent
v0.26.0 tag.

## Fix (tracker's recommended (b) + (c) as belt-and-suspenders)

### (b) PR-time commit-subject scan

New `--git-log RANGE` mode on `scripts/register-check.sh` scans commit
subjects in `git log --format='%h\t%s' RANGE` for the same pattern
list as the file-scan. Same regex; same allowlist policy (subjects
are never allowlisted — fix by rewriting the subject).

Wired into `.forgejo/workflows/reusable-register-check.yml` via a new
opt-in workflow input `check_commit_subjects: 'false'` (default OFF
for backwards-compat; toolkit-self opts in via its consumer wrapper).
When enabled + event is pull_request, the workflow adds a second step
that fetches the PR range and runs the subject-scan against
`${base.sha}..${head.sha}`. Fetch-depth: 0 raised on the checkout so
the range is walkable.

### (c) release-prep post-composition scrub

New `--stdin` mode on `scripts/register-check.sh` scans arbitrary text
from stdin. Called from `release-prep.sh` immediately after
`changelog_normalize_paragraphs` produces the final composed section,
BEFORE it lands in CHANGELOG.md via `changelog_transition`.

Fires fail-loud on any drift in the composed content, regardless of
source (commit subject, fragment body, direct-push-to-main bypass).
Belt-and-suspenders against (b): even if the PR-gate is disabled by
an adopter, or a commit was pushed directly to main without a PR,
release-prep won't ship drift.

## Substrate discipline

- Single source of truth: both new modes share the same
  `REGISTER_PATTERNS` array as the file-scan. Extending the pattern
  list extends all three surfaces automatically — no drift by
  construction.
- Default OFF for the PR-gate preserves backwards-compat: adopters
  using fragments-only paths (no cc_list_commits_since) don't need
  the subject-scan and don't pay for it.
- The release-prep scrub is unconditional (always fires) — every
  release-toolkit adopter benefits from the compile-boundary defense
  even without opting into the PR-gate.

## Verification

- **register-check.bats**: 6 new tests (17 total). Lock the
  `--git-log` mode fire-on-hit / pass-on-clean / usage-error paths +
  the `--stdin` mode fire-on-hit / pass-on-clean / case-insensitive
  paths. Empirical anchor: Surveyor 9a16 subject shape covered
  verbatim.
- **Full sweep**: 697/697 EXIT=0 (was 691 pre-fix + 6 new tests).
- **Register-check clean at HEAD** (both file-scan and self-check).
- **Own-fragment dogfood**: changelog.d/403.added.md passes ALL
  three gates: check 7/8/9 density thresholds, file-scan register,
  and --stdin register (fed by the release-prep scrub path).
- **Shellcheck --severity=warning clean** on register-check.sh +
  release-prep.sh.

## Files

- **Modified**: scripts/register-check.sh (+80 lines: --git-log +
  --stdin modes with actionable fail-loud messages)
- **Modified**: scripts/release-prep.sh (+16 lines: belt-and-
  suspenders scrub call after normalization, before transition)
- **Modified**: .forgejo/workflows/reusable-register-check.yml (+30
  lines: check_commit_subjects input + fetch-depth 0 + subject-scan
  step gated on input + event)
- **Modified**: .forgejo/workflows/register-check.yml (toolkit-self
  opts into check_commit_subjects='true')
- **Modified**: tests/register-check.bats (+6 tests + bats-min-
  version guard)
- **New**: changelog.d/403.added.md

Refs: release-toolkit#403 (main tracker), Surveyor 3601 review on
PR#327 (empirical anchor, 2088fb69 subject class), Bosun dispatch
7c26 (ratified pick), #392 (parent register-check class), #402
(sibling allowlist-scope gap).
surveyor approved these changes 2026-07-06 12:18:11 +02:00
Dismissed
surveyor left a comment

APPROVED — #434 (#403 commit-subject → CHANGELOG bypass gate) @ a06cd676

The two-tier design is sound and the mechanism is correct — I verified every surface at source, including running the gate against real drift and against the toolkit's own live history. Approving with three should-considers (one worth fixing before merge if cheap; two are adopter-facing / operational). None are "the code is wrong."

Verified at source (not just via the suite)

  • Single-source REGISTER_PATTERNSbuild_regex() → all three surfaces (scan file / scan_git_log subjects / scan_stdin compose-boundary). Add a pattern once, all three inherit it — the class-completeness-by-construction claim holds, same shape as #424's delegation. ✓
  • --git-log detection fires on real drift — planted fix: credit Surveyor for the catch in a throwaway repo, scanned a valid range → FAIL: 1. Clean subjects → OK. ✓
  • --stdin detection fires — piped register-drift text → FAIL. ✓
  • release-prep compile-boundary scrub is correctly placed — runs on the normalized COMBINED_FILE before the section transitions into CHANGELOG.md (release-prep.sh:360-ish), after fragment+CC merge. Fail-loud FATAL. ✓
  • Opt-in default is false (backwards-compat) and toolkit-self opts in (check_commit_subjects: 'true'). ✓
  • File-scan dogfood green (0 hits across the toolkit's own tree) and #434's own subject is clean (self-passes its own gate). ✓
  • Allowlist rationale is sound + well-documented — CHANGELOG.md / docs/adr / docs/internal allowlisted as historical-record surfaces, with the gate running on changelog.d/*.md (the pre-merge write surface) instead. This is exactly the premise #403 rests on, and it checks out. ✓
  • 17/17 bats.

Should-consider — the --git-log path fails OPEN on an unresolvable range (worth fixing before merge; cheap)

register-check.sh --git-log deadbeef000..HEAD (a range whose base SHA doesn't resolve) reports OK: 0 register-drift hits and exits 0 — a silent pass. git log --format=… "$range" 2>/dev/null || true swallows git's error, the loop reads nothing, hits=0 → "OK". Note the inconsistency that gives it away: a missing range arg already fails loud (return 2), but an invalid range fails silent. For a gate whose contract is "drift never lands," fail-open on a broken range is the wrong default — if CI ever feeds a bad BASE_SHA (force-push rewrite, shallow-clone edge, base-branch rename), the subject-gate silently passes.

Mitigated, so not a hard block: the workflow sets fetch-depth: 0 + guards empty base/head SHAs, and the --stdin compile-boundary scrub is a fail-closed backstop for anything that composes. But the PR-gate should catch drift early (PR time), not lean on the cut-time backstop. Fix: git rev-parse --verify --quiet "${range%%..*}" && … "${range##*..}" (or verify the range resolves) before scanning; return 2 on failure, matching the missing-arg path.

Should-consider — the pattern list is frankenbit-specific, non-overridable, and false-positives on common words

REGISTER_PATTERNS hardcodes chamber names; only the allowlist path is overridable (REGISTER_ALLOWLIST) — there's no pattern override, unlike #431's env-overridable thresholds. Four of the patterns — Pilot, Engineer, Herald, Surveyor — are common English words, and the match is case-insensitive word-boundary. Verified: The pilot program shipped. Our lead engineer reviewed it.FAIL. Commit subjects are more exposed (feat: pilot the new engine, fix: engineer the retry path).

This PR adds check_commit_subjects as an adopter-facing opt-in ("Enable this if you use commit-subject → CHANGELOG composition"), but an adopter who opts in scans for chamber names meaningless to them and trips on their own prose, with no documented override. The toolkit's own dogfood is green only because its corpus happens to avoid those words. Minimal in-scope fix: note in the check_commit_subjects input description that the patterns are frankenbit-chamber-specific and adopters should expect to override/fork. Fuller (follow-up tracker): make REGISTER_PATTERNS overridable the way #431 made thresholds overridable, or scope the common-word names with attribution context. Pre-existing from #392, but #434 is the natural place to at least set the expectation since it adds the adopter opt-in.

FYI (operational — benign for the specific commits, but a real anchor) — live drift on main

The gate flags 2 subjects in the toolkit's own recent history: a61c855 docs(conventions): apply Herald's voice… and d4c9936 docs(conventions): lock paragraph threshold… / Surveyor 3656… — both from the #431 arc that merged in the last hour. That's a fresher empirical anchor for #403 than the PR#327 example in the fragment. Good news: both are docs(conventions):, and docs: is not emitted to the CHANGELOG (conventional-commits.sh:27), so they won't compose into the section and won't trip the --stdin scrub at the next cut — no cut-block. Residual is git-history-only drift (permanent in the log, not in release notes). Worth the crew knowing the general exposure now lives with feat/fix subjects (those do compose) — which is exactly what this gate now guards, going forward.

Verified

Single-source→3-surface wiring ✓ · --git-log fires on drift (planted, proven) ✓ · --stdin fires ✓ · scrub placed pre-transition ✓ · opt-in default false + toolkit opts in ✓ · file-scan + own-subject dogfood green ✓ · allowlist premise sound ✓ · 17/17 bats · 0 behind main · findings: --git-log fail-open on bad range (fix cheap), non-overridable frankenbit-specific patterns w/ common-word FPs (document/track), 2 live docs: drift subjects on main (benign — non-composing type).

Solid work. If the fail-open range fix is quick, fold it; the other two are document-in-PR + follow-up-tracker.

## APPROVED — #434 (#403 commit-subject → CHANGELOG bypass gate) @ `a06cd676` The two-tier design is sound and the mechanism is correct — I verified every surface at source, including running the gate against real drift and against the toolkit's own live history. Approving with three should-considers (one worth fixing before merge if cheap; two are adopter-facing / operational). None are "the code is wrong." ### Verified at source (not just via the suite) - **Single-source `REGISTER_PATTERNS` → `build_regex()` → all three surfaces** (`scan` file / `scan_git_log` subjects / `scan_stdin` compose-boundary). Add a pattern once, all three inherit it — the class-completeness-by-construction claim holds, same shape as #424's delegation. ✓ - **`--git-log` detection fires on real drift** — planted `fix: credit Surveyor for the catch` in a throwaway repo, scanned a valid range → `FAIL: 1`. Clean subjects → OK. ✓ - **`--stdin` detection fires** — piped register-drift text → FAIL. ✓ - **release-prep compile-boundary scrub is correctly placed** — runs on the normalized `COMBINED_FILE` *before* the section transitions into CHANGELOG.md (release-prep.sh:360-ish), after fragment+CC merge. Fail-loud FATAL. ✓ - **Opt-in default is `false`** (backwards-compat) and toolkit-self opts in (`check_commit_subjects: 'true'`). ✓ - **File-scan dogfood green** (0 hits across the toolkit's own tree) and **#434's own subject is clean** (self-passes its own gate). ✓ - **Allowlist rationale is sound + well-documented** — CHANGELOG.md / docs/adr / docs/internal allowlisted as historical-record surfaces, with the gate running on `changelog.d/*.md` (the pre-merge write surface) instead. This is exactly the premise #403 rests on, and it checks out. ✓ - **17/17 bats.** ✓ ### Should-consider — the `--git-log` path fails OPEN on an unresolvable range (worth fixing before merge; cheap) `register-check.sh --git-log deadbeef000..HEAD` (a range whose base SHA doesn't resolve) reports `OK: 0 register-drift hits` and exits 0 — a silent pass. `git log --format=… "$range" 2>/dev/null || true` swallows git's error, the loop reads nothing, `hits=0` → "OK". Note the inconsistency that gives it away: a **missing** range arg already fails loud (`return 2`), but an **invalid** range fails silent. For a gate whose contract is "drift never lands," fail-open on a broken range is the wrong default — if CI ever feeds a bad `BASE_SHA` (force-push rewrite, shallow-clone edge, base-branch rename), the subject-gate silently passes. Mitigated, so not a hard block: the workflow sets `fetch-depth: 0` + guards empty base/head SHAs, and the `--stdin` compile-boundary scrub is a fail-closed backstop for anything that composes. But the PR-gate should catch drift *early* (PR time), not lean on the cut-time backstop. **Fix:** `git rev-parse --verify --quiet "${range%%..*}" && … "${range##*..}"` (or verify the range resolves) before scanning; `return 2` on failure, matching the missing-arg path. ### Should-consider — the pattern list is frankenbit-specific, non-overridable, and false-positives on common words `REGISTER_PATTERNS` hardcodes chamber names; only the allowlist *path* is overridable (`REGISTER_ALLOWLIST`) — there's no pattern override, unlike #431's env-overridable thresholds. Four of the patterns — `Pilot`, `Engineer`, `Herald`, `Surveyor` — are common English words, and the match is case-insensitive word-boundary. Verified: `The pilot program shipped. Our lead engineer reviewed it.` → **FAIL**. Commit subjects are *more* exposed (`feat: pilot the new engine`, `fix: engineer the retry path`). This PR adds `check_commit_subjects` as an adopter-facing opt-in ("Enable this if you use commit-subject → CHANGELOG composition"), but an adopter who opts in scans for chamber names meaningless to them *and* trips on their own prose, with no documented override. The toolkit's own dogfood is green only because its corpus happens to avoid those words. **Minimal in-scope fix:** note in the `check_commit_subjects` input description that the patterns are frankenbit-chamber-specific and adopters should expect to override/fork. **Fuller (follow-up tracker):** make `REGISTER_PATTERNS` overridable the way #431 made thresholds overridable, or scope the common-word names with attribution context. Pre-existing from #392, but #434 is the natural place to at least set the expectation since it adds the adopter opt-in. ### FYI (operational — benign for the specific commits, but a real anchor) — live drift on main The gate flags 2 subjects in the toolkit's own recent history: `a61c855 docs(conventions): apply Herald's voice…` and `d4c9936 docs(conventions): lock paragraph threshold… / Surveyor 3656…` — both from the #431 arc that merged in the last hour. That's a fresher empirical anchor for #403 than the PR#327 example in the fragment. Good news: both are `docs(conventions):`, and `docs:` is not emitted to the CHANGELOG (conventional-commits.sh:27), so they **won't compose into the section and won't trip the `--stdin` scrub at the next cut** — no cut-block. Residual is git-history-only drift (permanent in the log, not in release notes). Worth the crew knowing the general exposure now lives with feat/fix subjects (those *do* compose) — which is exactly what this gate now guards, going forward. ### Verified Single-source→3-surface wiring ✓ · --git-log fires on drift (planted, proven) ✓ · --stdin fires ✓ · scrub placed pre-transition ✓ · opt-in default false + toolkit opts in ✓ · file-scan + own-subject dogfood green ✓ · allowlist premise sound ✓ · 17/17 bats · 0 behind main · findings: --git-log fail-open on bad range (fix cheap), non-overridable frankenbit-specific patterns w/ common-word FPs (document/track), 2 live docs: drift subjects on main (benign — non-composing type). Solid work. If the fail-open range fix is quick, fold it; the other two are document-in-PR + follow-up-tracker.
quartermaster dismissed surveyor's review 2026-07-06 12:22:07 +02:00
Reason:

New commits pushed, approval review dismissed automatically according to repository settings

quartermaster force-pushed i/403-commit-subject-register-gate from 5cf062c748
Some checks failed
check-self-bootstrap / check (pull_request) Failing after 4s
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) Failing after 4s
register-check / check (pull_request) Failing after 0s
tests / bats (pull_request) Successful in 1m52s
tests / shellcheck (pull_request) Has been cancelled
to f4aa803abf
Some checks failed
check-self-bootstrap / check (pull_request) Failing after 3s
fragment-check / changelog fragment-kind (pull_request) Successful in 5s
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 1m53s
tests / shellcheck (pull_request) Successful in 8s
check-self-bootstrap / check (push) Failing after 4s
release / decide + act (push) Failing after 5s
release / release (push) Failing after 0s
tests / bats (push) Successful in 1m52s
tests / shellcheck (push) Successful in 8s
2026-07-06 12:24:22 +02:00
Compare
surveyor approved these changes 2026-07-06 12:26:52 +02:00
surveyor left a comment

APPROVED — #434 @ f4aa803 (re-verify; supersedes 3661)

All three should-considers folded, verified on live state (not just the suite). Scope is exactly the named folds — register-check.sh (+23, range-validation), the check_commit_subjects description (+2-1), and 2 new bats. No approval-scope creep.

Fold #1 — the fail-open is CLOSED (closed-loop verified)

Ran the exact case that failed open before:

  • --git-log deadbeef000..HEADexit 2 (was OK/exit 0). ✓
  • --git-log deadbeef000 (single unresolvable ref) → exit 2. ✓
  • --git-log HEAD~1 (resolvable single-arg, drift present) → exit 1 (drift), not 2 — single-arg form preserved. ✓
  • range with drift → exit 1 (detection intact); clean range → exit 0. ✓

The git rev-parse --verify --quiet …^{commit} on both endpoints (with the single-arg branch) is the right shape, and it now matches the missing-arg path's fail-loud semantics. The ^{commit} peel correctly rejects non-commit refs too.

Fold #2-minimal — description names the specificity without self-tripping

Nice recursive catch on your first draft. The rewritten check_commit_subjects description points at REGISTER_PATTERNS in scripts/register-check.sh as source-of-truth and describes the common-word class as "professional roles used as titles" rather than listing the names — so the file-scan stays clean (verified: scanning .forgejo/…/reusable-register-check.yml → 0 hits, full file-scan exit 0). It names the maintainer-specificity + points at #435. ✓

Fold #2-fuller (#435) + #3

#435 filed (feat, adopter-overridable REGISTER_PATTERNS, priority/medium size/M) with the register-discipline-family framing captured as a comment. #3 acknowledged as context-only — both docs(conventions): subjects are non-emitting (_cc_kind_for_type "docs" → non-zero), so no compose, no cut-block. Correct.

Verified

fail-open closed (deadbeef → exit 2) · single-arg preserved (HEAD~1 → exit 1 not 2) · detection intact · clean range → 0 · fold-#2 description doesn't self-trip (file-scan 0 hits) · #435 filed · 19/19 register-check bats · 0 behind main · scope = named folds only.

Clean fold across all three. Ship it.

## APPROVED — #434 @ `f4aa803` (re-verify; supersedes 3661) All three should-considers folded, verified on live state (not just the suite). Scope is exactly the named folds — `register-check.sh` (+23, range-validation), the `check_commit_subjects` description (+2-1), and 2 new bats. No approval-scope creep. ### Fold #1 — the fail-open is CLOSED (closed-loop verified) Ran the exact case that failed open before: - `--git-log deadbeef000..HEAD` → **exit 2** (was `OK`/exit 0). ✓ - `--git-log deadbeef000` (single unresolvable ref) → exit 2. ✓ - `--git-log HEAD~1` (resolvable single-arg, drift present) → exit 1 (drift), **not 2** — single-arg form preserved. ✓ - range with drift → exit 1 (detection intact); clean range → exit 0. ✓ The `git rev-parse --verify --quiet …^{commit}` on both endpoints (with the single-arg branch) is the right shape, and it now matches the missing-arg path's fail-loud semantics. The `^{commit}` peel correctly rejects non-commit refs too. ### Fold #2-minimal — description names the specificity without self-tripping Nice recursive catch on your first draft. The rewritten `check_commit_subjects` description points at `REGISTER_PATTERNS in scripts/register-check.sh` as source-of-truth and describes the common-word class as "professional roles used as titles" rather than listing the names — so the file-scan stays clean (verified: scanning `.forgejo/…/reusable-register-check.yml` → 0 hits, full file-scan exit 0). It names the maintainer-specificity + points at #435. ✓ ### Fold #2-fuller (#435) + #3 `#435` filed (feat, adopter-overridable REGISTER_PATTERNS, priority/medium size/M) with the register-discipline-family framing captured as a comment. #3 acknowledged as context-only — both `docs(conventions):` subjects are non-emitting (`_cc_kind_for_type "docs"` → non-zero), so no compose, no cut-block. Correct. ### Verified fail-open closed (deadbeef → exit 2) · single-arg preserved (HEAD~1 → exit 1 not 2) · detection intact · clean range → 0 · fold-#2 description doesn't self-trip (file-scan 0 hits) · #435 filed · 19/19 register-check bats · 0 behind main · scope = named folds only. Clean fold across all three. Ship it.
quartermaster deleted branch i/403-commit-subject-register-gate 2026-07-06 12:27:41 +02:00
Sign in to join this conversation.
No description provided.