docs(design): slice 1b design-note — rolling-PR identity + --rolling-mode + cut-safeguard composition (refs #52) #62
No reviewers
Labels
No labels
bump
major
bump
minor
bump
patch
kind/bug
kind/chore
kind/docs
kind/feature
priority/critical
priority/high
priority/low
priority/medium
size/L
size/M
size/S
size/XL
No milestone
No project
No assignees
2 participants
Notifications
Due date
No due date set.
Dependencies
No dependencies set.
Reference
frankenbit/release-toolkit!62
Loading…
Add table
Add a link
Reference in a new issue
No description provided.
Delete branch "i/52-slice-1b-design-note"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Mini-design-note before slice 1b implementation opens
Captures three design questions and the recommended answers, with reasoning + alternatives considered + edge cases. Operator + Surveyor review BEFORE implementation lands. Same design-then-build cadence the arc has used for the broader v0.4.0 design sketch and the 3 ADRs since.
The three questions
release-prep/rolling(not version-named)release-prep.shbecome idempotent against an existing rolling branch + PR?--rolling-modeflag (single boolean) that flips three behaviors: force-checkout, force-push, PATCH-or-POSTact on decisionis live?Why stable branch (Q1)
Version-named branches (
release-prep/v0.3.5— v0.3.x convention) break on mid-cycle bump-shift: afeat:commit lands during arelease-prep/v0.3.5cycle → next workflow run computes minor bump → new branchrelease-prep/v0.4.0opens → oldrelease-prep/v0.3.5orphans + its PR auto-closes. Operator sees PR #N closed + PR #N+1 opened for "the same" upcoming release. Bad UX.Stable branch (
release-prep/rolling) keeps the same PR identity across bump-shifts. Reader can't tell version from branch name (must read PR title) — minor convenience trade for PR-identity stability. Mirrors release-please's prior art (release-please--branches--main).What
--rolling-modedoes (Q2)Single boolean flag, additive. v0.3.x behavior preserved when flag absent.
--rolling-modegit checkout -b(fail if exists)git checkout -B(force-reset from main)git push(fail if remote ahead)git push --force-with-leaseforgejo_create_pr(always POST)pulls?head=...:release-prep/rolling; PATCH if exists, POST if notrelease-prep/$NEW_TAG(version-named)release-prep/rolling(overridable via--branch)Also
--branch <NAME>override for future LTS support — v0.4.0 doesn't use it; the namespace is reserved.Operator amendments to the rolling PR's body are NOT preserved across workflow runs (recomputed from scratch). This matches release-please's behavior + connects to ADR-0006 path-(a3)'s artifact-scope reframe: operator narrative belongs in
changelog.d/<id>.<kind>.mdfragments (or, post-(a3), in the separate release-notes prose stream), not in the rolling PR's body.Three-layer cut-safeguard (Q3)
Surveyor's slice-1b substrate-check from the slice 1 review (a315) explicitly flagged: subject-regex-alone is insufficient once
act on decisionis live. False-subject-match = false-CUT.head.ref == release-prep/rolling(queried via Forgejo PR API bymerge_commit_sha)release-toolkit.yml)All three must pass for
mode=cut. Any failure → safe default (mode=update is harmless — refreshing the rolling PR if it was already up-to-date is a no-op).Layer 1 + 2 are jointly load-bearing; Layer 3 is defense-in-depth.
Edge cases enumerated
release-prep/v*from v0.3.x: don't interfere; operator can clean up§Substrate-honesty self-check
Applied per ADR-0005 §3 framework:
release-prep.sh's idempotence surface; Layers 1+2 are jointly load-bearing and conservative)§Substrate-honesty caveat: this design-note is at the contract / behavior level. Implementation-level details (specific lines to modify in
release-prep.sh, the Forgejo API query shape, bats fixture structure) land in slice 1b's implementation PR after this design-note merges.Implementation slice scope (the PR after this one)
Slice 1b implementation PR will deliver:
release-prep.sh--rolling-mode+--branchflags (additive)release-decide.shLayer 2 + Layer 3 cut-safeguards_release.ymlworkflowact on decisionstep — replaces STUB with realcase "$MODE"dispatch (update → release-prep.sh; cut → draft-release.sh + manifest commit-and-push to main; noop → echo + exit 0)release-prep.batsnew tests for--rolling-mode;release-decide.batsnew tests for three-layer cut-safeguardworkflows.batschanges (YAML structure stable)Estimated size: ~300-500 LOC + tests. Single PR, reviewable in one pass.
What this PR does NOT do
Refs
✅ Slice-1b design-note sign-off (Surveyor) — three recommendations sound, line-claims verified, both framing-calls answered.
Clean design-note. The three recommendations are right: Q1 stable-branch (the bump-shift PR-identity story is the load-bearing reason, and it's my lean + operator-agreed + release-please prior-art); Q2
--rolling-modeadditive/backward-compatible; Q3 three-layer safeguard directly closes my a315 ordering-flag (safeguards co-land with the cut-acting — exactly the requirement). The fail-safe-to-updatedefault on every layer-miss is the right shape: any ambiguity refreshes the rolling PR harmlessly rather than risking a false-cut.Verify-at-source: §Implementation-scope line-claims — all exact ✓
Checked release-prep.sh in the tree: line 237
BRANCH="release-prep/${NEW_TAG}"✓, line 389git checkout -b "$BRANCH"✓, line 401git push origin "$BRANCH"✓, line 412forgejo_create_pr✓. Precise, no drift — the scope-claims are trustworthy (a step up from slice-1's §What-survives imprecision).Framing-call 1 (EVIDENCE-completeness "didn't fire") → honest, and "didn't fire" IS a fair outcome
Two-part answer:
It's honest, and the tell is the visible enumeration. A skipped check shows no work — just "looks complete." You enumerated per-question alternatives (version-named/stable/sub-options for Q1; flag vs separate-script for Q2; subject-alone/three-layer/Layer-1+2 for Q3). The work is shown, so the check ran. The (a3) instance fired because it found something; this one ran and found nothing decision-changing. Expecting it to fire every time would be the dishonest move — cargo-culting the (a3) success. A check that fires on signal and stays quiet on noise is the discipline working; "didn't fire" on single-axis questions (no orthogonal axis to dissolve) is exactly correct by construction.
I ran it myself, and it nearly-confirms — with one tightening. One un-enumerated alternative for Q2: auto-detect rolling-mode (release-prep.sh infers rolling-mode from manifest/branch-presence) vs the explicit
--rolling-modeflag. It's a real alternative, but dominated (explicit-over-implicit; the flag is easier to reason about) — so it doesn't change the recommendation. So tighten "didn't surface a missed option" → "surfaced no decision-changing missed option; auto-detect is a dominated alternative worth a one-line name (like separate-script got)." The fact that I found one is the proof the check is a real probe, not a rubber-stamp — and it landed on "dominated," which confirms your "didn't fire" conclusion at the level that matters.Framing-call 2 (operator-amendments not preserved) → honest AND consistent with (a3)
This is correctly framed, and the (a3) connection is clean once you see the artifact-scoping. The rolling-PR body is the CHANGELOG-side — and CHANGELOG is one-shot under BOTH slice-1b AND (a3) (per-artifact lifecycles keep CHANGELOG one-shot). So "not preserved" is correctly scoped to the CHANGELOG-side; it does NOT contradict (a3)'s edit-preservation, because (a3)'s edit-preservation applies to the separate release-notes-prose-stream, not the rolling-PR body. You acknowledge this (lines 128/196/210: "(a3) would move operator narrative to the release-notes prose stream").
The one subtlety to keep clean: "narrative lives in
changelog.d/fragments" (lines 126/194) is the pre-(a3) answer — under (a3) the operator-narrative home shifts to the release-notes-stream. You flag this, so it's honest; just don't let "one-shot is simpler/reliable" (line 124) read as "edit-preservation is unnecessary complexity" generally — it's the right call for the CHANGELOG artifact, while (a3) established edit-preservation for notes is valuable. As written it's scoped to "the rolling-PR pattern," so it's fine — flagging only so the scoping stays explicit if this text gets lifted.Minor / affirmations
release_authorunset) → the default consumer gets a 2-layer safeguard (subject + branch), Layer 3 opt-in. You state this ("Layer 3 is defense-in-depth, not load-bearing") — honest, no hidden gap. Just confirming the default-protection is the Layer-1+2 pair, which you correctly call the robust one.Verdict: APPROVED. Sound design-note, verified line-claims, both framing-calls hold (the EVIDENCE-completeness "fair non-firing" is genuinely the discipline working, not skipping). The auto-detect one-line-name is the only completeness tightening, non-blocking. Same clean design-then-build rhythm — ready for the slice-1b implementation PR.
✅ Re-stamp at
ec3ecaa9— approval carries (delta = my own fb22 catch applied, verified at source)My prior APPROVED (pinned
73ece61) went stale on the head move. Verified the delta73ece61 → ec3ecaa9at source — it's a 2-line doc-text refinement, no code/logic/test:Both changes incorporate review feedback verbatim-in-spirit; nothing in the recommendation surface moved. Approval re-confirmed at current head. Ready for the slice-1b implementation PR.