---
name: release-standard-bump-in-pr
description: "Release process standard v0.14.1 onward — bump rides in the release PR, tag on merge gated on green PR run"
metadata: 
  node_type: memory
  type: project
  originSessionId: eb2b5080-21d0-44ab-b08e-f5d1c9f2092f
  modified: 2026-07-30T01:29:25.430Z
---

NEW RELEASE STANDARD (operator-directed via doyle, 2026-06-23), applies **v0.14.1 ONWARD**.

**Change:** STOP authoring the version bump as a separate direct-push to main and waiting for a second full `push:[main]` CI pass before tagging. That re-runs the whole ~11min × 2-runner suite on byte-identical code (only version string + CHANGELOG + first-party Cargo.lock lines changed) — pure latency.

**⭐ NARROWED 2026-07-21 (doyle ruling, deployah-filed runbook defect):** the feature-PR's-final-commit form is DELETED — a **dedicated release PR off main is the ONLY form**. Reasons: (1) gate separation — feature PR gated on content by its gater, release PR gated on release shape (counter/changelog/bump/regen) by release driver; stacking fuses gates and the tagged commit is never validated AS a release commit; (2) counter freshness — counter read against published metadata at release-PR time, not frozen in a wave branch; (3) the original no-second-CI-wait goal is preserved (release PR CI validates the exact tagged commit). RELEASE-RUNBOOK step 1 carried the stale OR-form until deployah's fix PR (2026-07-21).

**New flow (v0.14.1+):**
1. Put the bump IN the release PR — ~~the feature PR's final commit, OR~~ a dedicated release PR (see NARROWED above). Bump = `Cargo.toml` workspace.package.version + `Cargo.lock` first-party `spt-*` lines + CHANGELOG section + `xtask gen` regen.
2. The PR's CI validates the EXACT commit that gets tagged.
3. On merge → tag straight away, gated on the green PR run.
4. The merge still fires `push:[main]` CI automatically — let it run as a NON-BLOCKING backstop; do NOT wait for it to tag.

Net: removes the serial second-CI wait.

**v0.14.0 was the LAST cut under the old flow** (separate bump-push to main @6ba875e + second push:[main] CI before tag) — finished as-is, not retro-changed. doyle is encoding this in `docs/RELEASE-RUNBOOK.md` (PR).

## ⚖ SUPERSEDED UNDER GOLDEN CI — doyle ruling 2026-07-29 (v0.46.0), doctrine fork, do NOT re-litigate

**`tested==shipped` OUTRANKS bump-in-PR.** Under ADR-0050 golden CI the two standards above cannot both hold, and this is the ruled precedence:

- ADR-0050 requires the TESTED sha == the SHIPPED sha (the point of ff-only main).
- The runbook requires the bump in its own release PR so the tagged commit is validated *as a release commit*.
- A thin release PR stacked on the ruled tip would put the TAG on a commit **no golden run ever tested** — the runbook's own stated fear arriving through the other door. It trades a gate that was PERFORMED but not FORMALIZED for a tagged sha that is genuinely unvalidated. Strictly worse.

**So: the bump RIDES THE BATCH, and the release-driver gate is satisfied by a RECORDED out-of-band audit at the ruled tip** — counter decoded from published *signed* metadata, changelog audited against the real commit range, compat axes proven from the constants at that sha, bump level justified from observable behaviour change. The form is retired, not the substance. I did the gate; I just don't file it as a PR.

**⚠ THE CONSEQUENCE THAT BINDS THE RELEASE DRIVER: there are NO post-golden edits. None.** Any changelog delta — including a one-character CHANGELOG **date** re-stamp — creates a commit after the tested tip and breaks tested==shipped. So:
- The changelog must be correct **at the assembled tip, before the builder pushes.**
- Raise every delta to the gater **BEFORE the push** so it rides the batch and gets tested with everything else.
- After the golden run the tree is FROZEN: the only legal remedies are a new batch commit **plus a re-run**, or shipping what was tested.
- ⭐ The date trap specifically: the `## [x.y.z] - DATE` line becomes the GitHub Release notes verbatim, so a ruling that lands after local midnight ships a wrong date that the freeze forbids fixing. Decide the date before the push, not at tag time.

### ⚖ THE DATE LEG — ruled 2026-07-29 (doyle), binding on ME (todlando) as ASSEMBLER, every assembly

The date trap above got a MECHANISM instead of a warning, because the decision point is **ASSEMBLY, not GO**, and it is exactly the item that gets dropped while attention is on classifying a red:

**DATE LEG: at assembly time, re-measure the local date and confirm the CHANGELOG `## [<version>]` heading matches it. If it does not, the correction rides the batch as a commit.**

Run it like the twohost-condition re-read and the trailer check — a named pre-push leg, NOT something recalled from a message. **Re-measure; never carry the date forward from a peer's figures or an earlier message** — that is [[measure-the-free-quantity-dont-derive-it]], which bit deployah the same night (a midnight margin sized off a stale log `epoch_ms`, 1.6h out). A date stamped from a remembered clock is that same defect with an *unrecoverable* consequence, because the post-golden freeze forbids the one-character fix afterward.

⭐ **Audit the batch delta by DIFF, never by commit label.** In this release the repair batch's `ci:`/`test:`-prefixed commits touched three files under `src/` (`cli.rs`, `daemon_inhibit.rs`, `servicehost.rs`). All three turned out to be test-scope or pure refactor and owed no entry — but that was established by reading the diffs, and a user-facing change hiding under a `test:` prefix is unfixable once the tree freezes.

### ⚖ THE DRIVER'S CHECK SET — v0.46.0, and the split that matters

The recorded audit is TWELVE checks, and they are not all the same kind:

- **SHA-PINNED (9)** — ancestry of the required repair tip, load-bearing file BLOBS, source occurrence counts, compat constants, lockfile diff, version/heading, deferral absence, workflow conditions. These cannot move while everyone holds. Re-run them, but a green run cannot change them.
- **TIME-VARYING (2)** — the **counter** (derived from "nothing published since <last>", which the releases repo can invalidate with nobody here seeing it) and the **date** (wall clock). ⭐ These do not drift because of the run; they drift because of the world. Someone saying "nothing in a test result touches the counter or the date" is true of the RUN and quietly reclassifies them as pinned. **Re-READ both at the moment of use; never re-cite an earlier decode.**
- **POST-PUSH (1)** — `git merge-base --is-ancestor <tip> origin/main` must exit 0 AFTER the push, read from a fresh fetch of origin, not local tracking. This is the assertion that catches **a push that silently did not land** — `release.yml` builds off the tag regardless, so nothing fails loudly while origin/main lags a published release. Also assert `git rev-parse origin/main` == tip *exactly*, so a merge commit cannot pass as a ff.

⭐⭐ **CHECK TWELVE, and it belongs FIRST: audit the user-facing artifact against its own voice rule, by grep, before signing.** Nine checks ask whether the tree is the right tree; **exactly ONE artifact in a release is written by us for a human to read**, and in v0.46.0 it was the only one never audited against its own binding standard. An audit against the RANGE and against CORRECTNESS is not an audit against your own rule. Costs one grep. Found real new drift: 1 second-person line in 0.46.0 against 0 in each of 0.45.0/0.44.0/0.43.1/0.43.0/0.42.0 — measured, which is what makes "this is drift, not habit" refusable.
⭐ **Read the grep's hits, don't count them.** The same audit flagged `perch` and `spool`; both are the PRODUCT's user-facing nouns, not mechanism leaking from the fix's vocabulary. Calling those violations would have cried wolf and taught me to distrust my own instrument.

### ⚖ NEVER EDIT A PUBLISHED RELEASE BODY (binding on me, ruled 2026-07-29)

The published release body is built from the tagged CHANGELOG section **verbatim**. So editing it after the fact makes the PUBLIC artifact differ from the content of a SIGNED tag — artifact divergence between a provenance record and the thing users read. **Refuse on principle, not on cost:** it would still be wrong if a golden cycle were free. Wrong notes get a new tree and a new tag, or they wait for the next release. Consistency between the tag's content and the published notes outranks the fix.

⚠ **The freeze is a property of the TREE, not of run timing** — my own error, 2026-07-29. I attached a deadline to a changelog decision ("decide before the seam legs conclude or it costs two cycles"). There is no second cycle: under the freeze ANY changelog edit needs its own green run whether the current run ends green, red, or cancelled. I reasoned about a run being part-way paid down when a golden run is binary — the shipping tree either has a green run or it does not. **A manufactured deadline is a way of pressuring the gater**, and it discredits an otherwise correct recommendation. State the finding, give the recommendation, attach no urgency the facts do not carry.

doyle owns the follow-up amendment to `docs/RELEASE-RUNBOOK.md` + ADR-0050 naming this precedence. Until it lands, **the runbook text still reads the retired form** — do not let it re-open the question.

Relates to [[v0140-endpoint-creation-flow]], [[golden-ci-strategy]], [[changelog-scope-vs-commit-range]], [[release-counter-from-published]], [[release-updateset-stale-window]]. Publisher discipline (counter-from-`.release.json`, first-party-Cargo.lock-only, doyle CHANGELOG-vet) unchanged.
