feat(release): configurable publish mode — draft-first (default) vs immediate-publish #114

Closed
opened 2026-06-26 14:37:28 +02:00 by quartermaster · 1 comment

Why

Currently draft-release.sh always creates the release with draft: true — the operator clicks Publish on the draft as Gate-3 of the multi-stage operator-gate pattern from ADR-0003. This is the substrate-honest default for high-stakes releases (consumer-facing, semver-significant).

But for some consumer scenarios, the Gate-3 click is friction without benefit:

  • Internal-only CI tags that consumers don't pull from
  • High-frequency point releases on consumer-internal services
  • CI/CD pipelines where the rolling PR merge IS the publish-intent signal (no separate "review the draft" step adds value)

Adding a configurable publish mode lets each consumer pick the gate-pattern that matches their cadence.

Proposed surface

_release.yml workflow_call input

publish_mode:
  description: 'Publish mode (draft|immediate) — draft (default) creates Gate-3 click; immediate publishes directly after cut'
  required: false
  default: 'draft'
  type: string

release.yml consumer template

Forwarded the same way as other inputs (e.g., bump_override, dry_run).

draft-release.sh

Reads PUBLISH_MODE env var; when immediate, passes "draft": false to the Forgejo API call.

release-toolkit.yml config (alternative or additional)

Consumers could also set the mode in their config file rather than per-workflow-call. Decision question for design: per-workflow-call only, per-config only, or both? (Per-workflow-call is simpler + matches existing inputs; per-config locks the cadence at the consumer level.)

Substrate-honest defaults

  • Default: draft — preserves ADR-0003's multi-stage-gate design. Consumers who don't think about this get the safe default.
  • Opt-in: immediate — explicit consumer decision to skip Gate-3 for their cadence. Documented in docs/integration.md (likely alongside #112's review-gate tradeoff section).

Composition with existing gates

Gate Trigger Configurable?
Gate-1 Rolling PR merge No — always operator-click
Gate-2 (none currently)
Gate-3 Publish draft release This featurepublish_mode opt-in to skip

Composition with #112 review-gate tradeoff

The pre-v1.0 readiness sweep (#106) is accumulating a family of per-consumer configuration questions:

  • #112: review-gate tradeoff — required_approvals 0 vs 1+ (which gates the manifest auto-merge)
  • #113: auto-assign manifest follow-up PR to rolling-PR merger (UX touch under option (3))
  • #114 (this issue): publish mode — draft vs immediate (which gates Gate-3)

A unified "deployment cadence" docs section in docs/integration.md could collect all three under "configurable cadence choices each consumer should make on adoption."

What this PR (when implemented) does NOT do

  • Does not change ADR-0003 — the default remains draft-first; this is an opt-in mode addition
  • Does not change the rolling PR mechanism — Gate-1 remains operator-clicked
  • Does not change tag creation — tags are created when the release is published (either as draft+publish or immediate)

Test coverage needed

  • bats tests in tests/draft-release.bats:
    • publish_mode=draft → draft=true in API call (existing behavior, regression guard)
    • publish_mode=immediate → draft=false in API call
    • publish_mode unset → defaults to draft (backwards-compat)
    • publish_mode=invalid → fail-loud with clear message

ADR consideration

The default-stays-draft + opt-in-to-immediate shape doesn't conflict with ADR-0003's substrate-honest framing. But the configurable-cadence-family (this issue + #112 + #113) might warrant a new ADR (ADR-0007 candidate) about "configurable per-consumer cadence patterns" — surface the family as a primitive rather than three independent ad-hoc features.

Decision-deferral: file the ADR question when one of the three lands as the first implementation; that's the earliest moment we know what the actual abstraction wants to be.

Refs

  • Operator framing 2026-06-26: "would it make sense to make it configurable, if the cut releases should be draft first, or be directly released/published after the cut?"
  • ADR-0003: multi-stage operator gate (current default origin)
  • Sibling trackers: #112 review-gate tradeoff, #113 auto-assign manifest follow-up
  • Pre-v1.0 readiness sweep: #106 (this is one of the family of configurable-cadence questions)
## Why Currently `draft-release.sh` always creates the release with `draft: true` — the operator clicks Publish on the draft as Gate-3 of the multi-stage operator-gate pattern from [ADR-0003](docs/adr/0003-multi-stage-operator-gate.md). This is the substrate-honest default for high-stakes releases (consumer-facing, semver-significant). But for some consumer scenarios, the Gate-3 click is friction without benefit: - Internal-only CI tags that consumers don't pull from - High-frequency point releases on consumer-internal services - CI/CD pipelines where the rolling PR merge IS the publish-intent signal (no separate "review the draft" step adds value) Adding a **configurable publish mode** lets each consumer pick the gate-pattern that matches their cadence. ## Proposed surface ### `_release.yml` workflow_call input ```yaml publish_mode: description: 'Publish mode (draft|immediate) — draft (default) creates Gate-3 click; immediate publishes directly after cut' required: false default: 'draft' type: string ``` ### `release.yml` consumer template Forwarded the same way as other inputs (e.g., `bump_override`, `dry_run`). ### `draft-release.sh` Reads `PUBLISH_MODE` env var; when `immediate`, passes `"draft": false` to the Forgejo API call. ### `release-toolkit.yml` config (alternative or additional) Consumers could also set the mode in their config file rather than per-workflow-call. Decision question for design: per-workflow-call only, per-config only, or both? (Per-workflow-call is simpler + matches existing inputs; per-config locks the cadence at the consumer level.) ## Substrate-honest defaults - **Default: `draft`** — preserves [ADR-0003](docs/adr/0003-multi-stage-operator-gate.md)'s multi-stage-gate design. Consumers who don't think about this get the safe default. - **Opt-in: `immediate`** — explicit consumer decision to skip Gate-3 for their cadence. Documented in `docs/integration.md` (likely alongside [#112](https://git.frankenbit.de/frankenbit/release-toolkit/issues/112)'s review-gate tradeoff section). ## Composition with existing gates | Gate | Trigger | Configurable? | |---|---|---| | Gate-1 | Rolling PR merge | No — always operator-click | | Gate-2 (none currently) | — | — | | Gate-3 | Publish draft release | **This feature** — `publish_mode` opt-in to skip | ## Composition with [#112](https://git.frankenbit.de/frankenbit/release-toolkit/issues/112) review-gate tradeoff The pre-v1.0 readiness sweep ([#106](https://git.frankenbit.de/frankenbit/release-toolkit/issues/106)) is accumulating a family of per-consumer configuration questions: - **#112**: review-gate tradeoff — `required_approvals` 0 vs 1+ (which gates the manifest auto-merge) - **#113**: auto-assign manifest follow-up PR to rolling-PR merger (UX touch under option (3)) - **#114** (this issue): publish mode — `draft` vs `immediate` (which gates Gate-3) A unified "deployment cadence" docs section in `docs/integration.md` could collect all three under "configurable cadence choices each consumer should make on adoption." ## What this PR (when implemented) does NOT do - **Does not change [ADR-0003](docs/adr/0003-multi-stage-operator-gate.md)** — the default remains draft-first; this is an opt-in mode addition - **Does not change the rolling PR mechanism** — Gate-1 remains operator-clicked - **Does not change tag creation** — tags are created when the release is published (either as draft+publish or immediate) ## Test coverage needed - bats tests in `tests/draft-release.bats`: - `publish_mode=draft` → draft=true in API call (existing behavior, regression guard) - `publish_mode=immediate` → draft=false in API call - `publish_mode` unset → defaults to draft (backwards-compat) - `publish_mode=invalid` → fail-loud with clear message ## ADR consideration The default-stays-draft + opt-in-to-immediate shape doesn't conflict with ADR-0003's substrate-honest framing. But the configurable-cadence-family (this issue + #112 + #113) might warrant a new ADR (ADR-0007 candidate) about "configurable per-consumer cadence patterns" — surface the family as a primitive rather than three independent ad-hoc features. Decision-deferral: file the ADR question when one of the three lands as the first implementation; that's the earliest moment we know what the actual abstraction wants to be. ## Refs - **Operator framing 2026-06-26**: "would it make sense to make it configurable, if the cut releases should be draft first, or be directly released/published after the cut?" - **ADR-0003**: multi-stage operator gate (current default origin) - **Sibling trackers**: [#112](https://git.frankenbit.de/frankenbit/release-toolkit/issues/112) review-gate tradeoff, [#113](https://git.frankenbit.de/frankenbit/release-toolkit/issues/113) auto-assign manifest follow-up - **Pre-v1.0 readiness sweep**: [#106](https://git.frankenbit.de/frankenbit/release-toolkit/issues/106) (this is one of the family of configurable-cadence questions)
Author
Owner

Closing — implemented in v0.8.1 sprint + default-flipped in v0.9.1 sprint

  • Implementation: #119 (v0.8.1 sprint) introduced the publish_mode workflow input + PUBLISH_MODE env propagation + draft-release.sh mode handling
  • Default flip: #125 (v0.9.1 sprint, ADR-0003 amendment 2026-06-26) flipped default from draftimmediate per operator audience-framing
  • Live exercise: v0.8.1+ cuts on toolkit-self exercised draft mode; v0.10.0+ cuts exercised immediate mode; cellblock first cut (v1.1.0, 2026-06-26) exercised immediate-publish on path-γ with the default-flip live

Close-keyword from #119/#125 didn't fire; cleaning up the tracker now.

## Closing — implemented in v0.8.1 sprint + default-flipped in v0.9.1 sprint - **Implementation**: [#119](https://git.frankenbit.de/frankenbit/release-toolkit/pulls/119) (v0.8.1 sprint) introduced the `publish_mode` workflow input + `PUBLISH_MODE` env propagation + `draft-release.sh` mode handling - **Default flip**: [#125](https://git.frankenbit.de/frankenbit/release-toolkit/pulls/125) (v0.9.1 sprint, ADR-0003 amendment 2026-06-26) flipped default from `draft` → `immediate` per operator audience-framing - **Live exercise**: v0.8.1+ cuts on toolkit-self exercised draft mode; v0.10.0+ cuts exercised immediate mode; cellblock first cut (v1.1.0, 2026-06-26) exercised immediate-publish on path-γ with the default-flip live Close-keyword from #119/#125 didn't fire; cleaning up the tracker now.
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
frankenbit/release-toolkit#114
No description provided.