Skip to content

Release ritual documents a procedure branch protection made impossible #402

Description

@Polichinel

docs/guides/publishing_to_pypi.md and tests/test_falsification_deploy_v160.py describe a promotion procedure we no longer perform — and cannot perform.

What the record says

TestF2FastForwardMerge::test_main_is_ancestor_of_development docstring:

Quick Deploy says git merge development --ff-only. This requires main's HEAD to be an ancestor of development's HEAD. If not, the documented procedure fails with fatal: Not possible to fast-forward, aborting.

A fast-forward promotion never diverges: main becomes byte-identical to development, and the ancestor property holds for free. No back-merge step existed because none was needed. The gate is enforcing a precondition for that procedure.

What actually happens

Branch protection (2026-07-31, C-320) requires a PR into main. You cannot fast-forward through a GitHub PR — the UI offers merge-commit, squash, or rebase, and only merge-commit preserves the release SHAs. So the real procedure silently became merge-commit promotion, which leaves exactly one commit on main that is not on development, every single release.

So this is not a forgotten step. The documented command can no longer be executed, and the guide has never said so.

Why it went unnoticed

The gate that detects it is skipped in CI (shallow checkout can't answer merge-base — C-320), so it only fires when someone runs the full suite locally. After v1.10.0 that happened about four hours after the release, by accident. It has been silently true after every prior release.

Root cause

Third instance of the same failure mode in one session: the written record describing a world that changed underneath it.

Each was true when written. Each stopped being true without anything failing loudly.

Fix

  1. Update publishing_to_pypi.md — document merge-commit promotion (what we do), delete the --ff-only Quick Deploy text, and add the post-release back-merge.
  2. Correct the test docstring so it stops citing a dead procedure. The assertion stays valid; its rationale changes from "so --ff-only works" to "so branch topology stays meaningful and main..development keeps counting only unreleased work".
  3. Automate the back-merge — an Action on release: published that opens the sync PR (--base development --head main, merge-commit). Preferred over documenting a manual step: a step a tired operator must remember after every release is a step that will eventually be missed. That is precisely what happened here.
  4. Consider running the deploy gates in CI with fetch-depth: 0 on main-targeted PRs, so detection stops depending on someone running the suite locally at the right moment.

Item 3 makes items 1 and 2 mostly cosmetic — worth doing anyway so the record stops lying.

Stale-branch half of this issue is resolved: delete_branch_on_merge was false, now true (2026-08-02). Eleven merged branches had accumulated; they were deleted by hand and GitHub now removes them automatically.

Refs: #400 (fixes the v1.10.0 instance), C-320 (why the gate skips in CI), ADR-051.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions