docs(609): correct the --no-merges rationale — merges are discouraged, not blocked
Review Low x2, both correct and both the stale-comment class: The claim that prepush-rebase-check.sh forbids merging main into a PR branch is false. That hook refuses a branch that is BEHIND origin/main; a merge makes origin/main an ancestor, so the push is allowed. Merging main in is discouraged by convention only. So --no-merges does cost a real false negative: an author who marks ONLY a conflict-resolving merge commit gets a legitimate rewrite rejected. Keeping --no-merges and stating the trade explicitly -- that failure is loud and costs one extra commit, whereas honoring forge-composed merge bodies disables the guard silently, which is #609 itself. The module comment also still claimed a quoted example cannot arm the guard, which contradicts the residual the decision record now states accurately. Aligned both, and narrowed the record's Rule line from "some commit" to "some NON-MERGE commit" so the stated contract matches the implementation. No logic change -- comments, docstring, record prose and regenerated catalog only. fixes #609 Decisions-Edit: yes Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
+6
-3
@@ -3924,7 +3924,7 @@ owner's sweep **destroyed** the row the other library still served. This is the
|
||||
|
||||
## 2026-07-25 — The rationale-edit marker is a git trailer, not a substring anywhere in the commit range (#609)
|
||||
`key: ci.decisions-edit-trailer` · `status: active` · `since: 2026-07-25` · `supersedes: none` · `superseded-by: none`
|
||||
**Rule:** The body-diff exemption is armed by an affirmative `Decisions-Edit:` **git trailer** (`yes`/`true`/`1`, case-insensitive) on some commit in the PR's merge-base range — never by a substring search over the message text. A non-affirmative value (`no`) does not arm it, the retired `[decisions-edit]` substring arms nothing (the validator emits a `::warning::` nudge when it sees one without a trailer), and a git error leaves the guard ON.
|
||||
**Rule:** The body-diff exemption is armed by an affirmative `Decisions-Edit:` **git trailer** (`yes`/`true`/`1`, case-insensitive, read with `unfold`) on some NON-MERGE commit in the PR's merge-base range — never by a substring search over the message text. A non-affirmative value (`no`) does not arm it, the retired `[decisions-edit]` substring arms nothing (the validator emits a `::warning::` nudge when it sees one without a trailer), and a git error leaves the guard ON.
|
||||
**Signals:** decisions-edit, Decisions-Edit trailer, rationale-prose edit, body-diff guard, decisions_validate, vacuous gate, green no-op · paths: `scripts/decisions_validate.py`, `scripts/tests/test_decisions_validate.py` · issues: #609, #603, #521
|
||||
**Mechanics:** `git log --no-merges --format='%(trailers:key=Decisions-Edit,valueonly,unfold)' <mb>..<head>`; `dv._edit_trailer_armed`
|
||||
|
||||
@@ -3948,8 +3948,11 @@ way to record a deliberate non-edit, must not read as consent.
|
||||
**Three exactness requirements the naive trailer read still gets wrong** (all found in review, all
|
||||
regression-tested). `--no-merges`: on a `pull_request` event `actions/checkout` lands on a synthetic
|
||||
merge commit whose body the forge composes from the PR *description*, so without it a description
|
||||
ending in an example marker arms a guard no author armed (merging main into a PR branch is separately
|
||||
forbidden, so nothing author-written is skipped). `unfold`: a folded `Decisions-Edit: no` + continuation
|
||||
ending in an example marker arms a guard no author armed. That exclusion is not free: merging main
|
||||
into a PR branch is discouraged but not mechanically blocked (`prepush-rebase-check.sh` only refuses a
|
||||
branch that is *behind* main, and a merge makes main an ancestor), so an author who marks ONLY such a
|
||||
merge commit has a legitimate rewrite rejected — a LOUD failure costing one extra commit, deliberately
|
||||
preferred over silently disabling the guard. `unfold`: a folded `Decisions-Edit: no` + continuation
|
||||
` yes` otherwise yields two lines, and the continuation arms on its own — inverting the value the
|
||||
author wrote. And resolving the marker **fails closed** on a git error, the one deliberate exception
|
||||
to this module's fail-open posture: a fail-open marker lookup is #609 through a different door.
|
||||
|
||||
@@ -36,7 +36,7 @@ the link for rationale. Superseded/retired history lives in `archive/`. Regenera
|
||||
| `ci.batch-pushes-no-cancel-route` | Hold review fixes, doc corrections and format fixes locally and push **once** — a superseded run cannot be cancelled from the agent side and holds a runner slot until it finishes. | 2026-07-21 | [link](workflow-process.md#2026-07-21--batch-your-pushes-there-is-no-agent-side-cancel-route-on-gitea-1254-542) |
|
||||
| `ci.build-once-rejected` | CI build-once (a shared compile artifact across jobs) was implemented, measured, and rejected for a 40-85% wall-clock regression; keep the #420 cross-run tree-identity skip instead. | 2026-07-18 | [link](../decisions.md#2026-07-18--ci-build-once-was-measured-and-rejected-keep-the-420-tree-skip) |
|
||||
| `ci.cancelled-is-not-a-verdict` | Treat a `cancelled` conclusion as "no verdict" — never as pass or fail — and report FAILED and CANCELLED counts separately in any CI monitor. | 2026-07-21 | [link](workflow-process.md#2026-07-21--cancelled-is-not-failure-a-cancelled-run-is-no-verdict-542) |
|
||||
| `ci.decisions-edit-trailer` | The body-diff exemption is armed by an affirmative `Decisions-Edit:` **git trailer** (`yes`/`true`/`1`, case-insensitive) on some commit in the PR's merge-base range — never by a substring search over the message text. A non-affirmative value (`no`) does not arm it, the retired `[decisions-edit]` substring arms nothing (the validator emits a `::warning::` nudge when it sees one without a trailer), and a git error leaves the guard ON. | 2026-07-25 | [link](../decisions.md#2026-07-25--the-rationale-edit-marker-is-a-git-trailer-not-a-substring-anywhere-in-the-commit-range-609) |
|
||||
| `ci.decisions-edit-trailer` | The body-diff exemption is armed by an affirmative `Decisions-Edit:` **git trailer** (`yes`/`true`/`1`, case-insensitive, read with `unfold`) on some NON-MERGE commit in the PR's merge-base range — never by a substring search over the message text. A non-affirmative value (`no`) does not arm it, the retired `[decisions-edit]` substring arms nothing (the validator emits a `::warning::` nudge when it sees one without a trailer), and a git error leaves the guard ON. | 2026-07-25 | [link](../decisions.md#2026-07-25--the-rationale-edit-marker-is-a-git-trailer-not-a-substring-anywhere-in-the-commit-range-609) |
|
||||
| `ci.decisions-lifecycle-flake` | When `decisions lifecycle` is the **only** red job, do not investigate and do not create a new run to clear it — no rebase, no `--amend`, no no-op push; the operator reruns that single job from the Gitea UI. | 2026-07-21 | [link](workflow-process.md#2026-07-21--a-lone-decisions-lifecycle-red-is-a-known-infra-flake-do-nothing-542) |
|
||||
| `ci.docs-only-detect-shallow-safe` | The docs-only detect script must diff against `FETCH_HEAD` (always resolves after `git fetch`, even shallow) using a two-dot tree diff — not `origin/<base>` with three-dot — because a `fetch-depth: 1` shallow clone has no remote-tracking ref and no merge-base, which silently fails the original detect into `docs_only=false` (full matrix, no functional error). A CI-behavior change must be verified by measuring the effect (job durations), not just a green check. | 2026-07-17 | [link](../decisions.md#2026-07-17--docs-only-detect-must-be-shallow-checkout-safe-fetch_head--two-dot-not-originmain--three-dot-416-follow-up) |
|
||||
| `ci.docs-only-skip-steps` | A docs-only change must still run every required job (`test`, `migrations`) so their commit-status contexts always report; each heavy job runs `scripts/ci-detect-docs-only.sh` first and gates its real STEPS on `if: steps.detect.outputs.docs_only != 'true'`, never `if:`-skips the whole job (an `if:`-skipped job reports `skipped`, not `success`, which branch protection may never unblock on). Detection biases toward running more on any doubt. | 2026-07-17 | [link](../decisions.md#2026-07-17--docs-only-ci-skip-gates-steps-in-always-running-required-jobs-never-if-skips-them-416) |
|
||||
|
||||
Reference in New Issue
Block a user