Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
73 changes: 50 additions & 23 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2,35 +2,54 @@
# rationale (lets us bridge GitHub's NUGET_API_KEY secret to Fallout's
# NuGetApiKey parameter, which would otherwise have to share a name).
#
# Shape (post #274, milestone #13):
# Shape:
# - Trigger: tag push `v*` on a `release/v*` branch. Tag protection ruleset
# restricts who can create those tags to repo admins.
# - Optional fallback: workflow_dispatch with an existing-tag input, for
# re-runs after a transient publish-API failure.
# re-runs after a transient publish-API failure, OR to opt into a
# nuget.org publish (see below).
# - validate-ref → test-and-pack → fan out to three publish jobs in
# parallel, one per GitHub Environment / Tier:
# - publish-nuget-org (Tier 1, approval-gated, Fallout.* only)
# - publish-github-packages (Tier 2, Nuke.* transition shims)
# parallel:
# - publish-nuget-org (Tier 1, opt-in via workflow_dispatch flag, approval-gated)
# - publish-github-packages (Tier 2, all packages — Fallout.* + Nuke.*)
# - publish-github-releases (artifact bundling on the tag's GH Release)
#
# Tier philosophy (RFC #267): nuget.org = production-grade & slow,
# GitHub Packages = bleeding edge / beta, GitHub Releases = bundled artifacts.
# Channel decisions encoded in the per-job `dotnet nuget push` glob patterns.
# Channel routing for v11+ (current direction):
# - nuget.org is reserved for v10.x and (eventually) "stabilised" v11. Tag
# pushes do NOT auto-publish to nuget.org. To publish Fallout.* to
# nuget.org you must invoke workflow_dispatch with publish-to-nugetorg=true
# — a conscious "this release is stabilised enough for nuget.org" switch.
# - GitHub Packages gets every Fallout.* and Nuke.* build via tag push.
# This is the de-facto release channel during v11 prerelease/stabilisation.
# - GitHub Releases bundles nupkgs on the tag's release page regardless.
#
# When v11 stabilises (or for cutting v10.x maintenance patches), use the
# workflow_dispatch path with publish-to-nugetorg=true. See
# docs/branching-and-release.md for the full runbook.

name: release

on:
push:
tags:
- 'v*'
# Fallback for re-running a partial publish after a transient API failure.
# Pick an existing tag — the workflow checks it out and re-runs the publish
# fan-out. `--skip-duplicate` on each push makes re-runs idempotent.
# Manual fallback for two scenarios:
# 1. Re-running a partial publish after a transient API failure (pick the
# tag, leave publish-to-nugetorg at its default).
# 2. Opting into a nuget.org publish for a stabilised release (pick the
# tag, set publish-to-nugetorg to true). Still gated by the nuget-org
# env approval before the actual push.
# `--skip-duplicate` on each push makes re-runs idempotent.
workflow_dispatch:
inputs:
tag:
description: 'Existing tag to (re-)release (e.g. v11.0.5)'
required: true
publish-to-nugetorg:
description: 'Publish Fallout.* to nuget.org? Default false — opt-in for stabilised releases only.'
required: false
type: boolean
default: false

permissions:
contents: read
Expand Down Expand Up @@ -92,13 +111,19 @@ jobs:
retention-days: 7
if-no-files-found: error

# Tier 1 — production. Approval-gated via the nuget-org GitHub Environment.
# Only Fallout.* packages go here; Nuke.* shim IDs are owned by the original
# NUKE maintainer on nuget.org and are routed to GitHub Packages instead.
# Tier 1 — production / nuget.org. **OPT-IN ONLY.** Tag pushes do NOT trigger
# this job; you must invoke workflow_dispatch with publish-to-nugetorg=true.
# The env approval gate on `nuget-org` is the second layer — even after opting
# in via the input flag, you still approve the deployment.
#
# The opt-in defaults to false because v11 is currently published to GitHub
# Packages only (see header comment). When v11 "stabilises" — or for cutting
# a v10.x maintenance patch — invoke workflow_dispatch with the flag set.
publish-nuget-org:
name: publish → nuget.org
runs-on: ubuntu-latest
needs: [test-and-pack]
if: github.event_name == 'workflow_dispatch' && inputs.publish-to-nugetorg == true
environment:
name: nuget-org
url: https://www.nuget.org/profiles/Fallout
Expand Down Expand Up @@ -129,9 +154,11 @@ jobs:
--skip-duplicate
done

# Tier 2 — bleeding edge / beta. The Nuke.* transition shims live here per
# #47 (we don't own the Nuke.* IDs on nuget.org). Future-direction: also
# publish Fallout.* beta builds here on a faster cadence than nuget.org.
# Tier 2 — GitHub Packages. The de-facto release channel for v11. Pushes
# both Fallout.* AND Nuke.* transition shims. The Nuke.* shim IDs are owned
# by the original NUKE maintainer on nuget.org (#47) — GitHub Packages is
# their permanent home. Fallout.* is here because v11 is "off nuget.org" by
# default (see header).
publish-github-packages:
name: publish → GitHub Packages
runs-on: ubuntu-latest
Expand All @@ -151,16 +178,16 @@ jobs:
with:
name: packages
path: output/packages
- name: 'Push: Nuke.* transition shims to GitHub Packages'
- name: 'Push: all *.nupkg to GitHub Packages'
run: |
set -euo pipefail
shopt -s nullglob
shims=(output/packages/Nuke.*.nupkg)
if [ ${#shims[@]} -eq 0 ]; then
echo "No Nuke.* shim packages found — skipping."
exit 0
packages=(output/packages/*.nupkg)
if [ ${#packages[@]} -eq 0 ]; then
echo "::error::No packages found in artifact — nothing to publish."
exit 1
fi
for pkg in "${shims[@]}"; do
for pkg in "${packages[@]}"; do
echo "Pushing $pkg to GitHub Packages..."
dotnet nuget push "$pkg" \
--source "https://nuget.pkg.github.com/ChrisonSimtian/index.json" \
Expand Down
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,8 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.
- **`main`'s `version.json` major** stays at 11 for now ([#271](https://github.com/ChrisonSimtian/Fallout/issues/271) deferred until v12 work begins).
- **Tier 3 Docker local NuGet server** ([#279](https://github.com/ChrisonSimtian/Fallout/issues/279)) — pre-merge testing channel — landed as a separate work item; initial setup in PR [#287](https://github.com/ChrisonSimtian/Fallout/pull/287).

- **nuget.org publish is now opt-in for v11; GitHub Packages is the default release channel.** Tag pushes on `release/v11` publish to GitHub Packages + GitHub Releases only; the `publish-nuget-org` job is skipped unless `workflow_dispatch` is invoked with `publish-to-nugetorg=true` (and the existing `nuget-org` env approval gate still fires). `publish-github-packages` now pushes **all** `*.nupkg` (Fallout.* + Nuke.*), not just Nuke.* shims — GitHub Packages is the de-facto v11 release channel during stabilisation. nuget.org is reserved for v10.x maintenance lines and a future stabilised v11. Decision recorded in [`docs/adr/0002-v11-off-nuget-by-default.md`](docs/adr/0002-v11-off-nuget-by-default.md); maintainer runbook updated in [`docs/branching-and-release.md`](docs/branching-and-release.md). This *modifies* the routing originally documented in the milestone #13 umbrella entry above — the branching model and CD shape are unchanged, only the routing policy.

## [10.2.0] / 2026-05-21
The NUKE → Fallout hard fork. Originally NUKE by [@matkoch](https://github.com/matkoch) and contributors; under new maintenance and rebranded to Fallout.

Expand Down
95 changes: 95 additions & 0 deletions docs/adr/0002-v11-off-nuget-by-default.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
# ADR-0002: v11 publishes to GitHub Packages by default; nuget.org is opt-in

## Status

Accepted (2026-05-29).

## Context

[ADR-0001](0001-release-branch-model.md) established the tag-triggered, multi-channel CD pipeline. The original routing was:

- Tag on `release/v11` → publish Fallout.* to nuget.org (Tier 1) + publish Nuke.* shims to GitHub Packages (Tier 2) + attach all nupkgs to GitHub Release.

Shortly after that landed, Chris flagged a separate direction: **v11 should not go to nuget.org by default.** nuget.org is reserved for v10.x maintenance lines and a future "stabilised" v11. Existing `11.0.x` releases on nuget.org are being unlisted in parallel.

Reasons (not exhaustive — Chris's call):

- The v11 rebrand surface is still settling. Public-API consumers depending on Fallout.* via nuget.org get exposed to churn that hasn't fully stabilised.
- 10.x is still the de-facto stable line for most consumers; the project-controlled nuget.org space should remain v10.x-oriented until v11 has shipped a stable enough surface to assume the position.
- GitHub Packages is a fine release channel for beta-and-stabilising work — opt-in consumers add the feed to their `nuget.config`, the broader consumer base isn't dragged into rebrand churn.

The pipeline shipped in ADR-0001 routed Fallout.* to nuget.org on every tag — exactly the wrong default for the current v11 state.

## Decision

Make nuget.org publish **opt-in via a `workflow_dispatch` input flag**. Specifically:

- Add `publish-to-nugetorg` (boolean, default `false`) to the `release.yml` `workflow_dispatch` inputs.
- `publish-nuget-org` job gains `if: github.event_name == 'workflow_dispatch' && inputs.publish-to-nugetorg == true`.
- Tag pushes alone do NOT trigger nuget.org publish. They publish to GitHub Packages + GitHub Releases only.
- To publish Fallout.* to nuget.org, invoke `workflow_dispatch` with `publish-to-nugetorg=true`. The `nuget-org` env approval gate still fires before the actual push — two layers of intent.

Companion change: **`publish-github-packages` now pushes all `*.nupkg`**, not just `Nuke.*`. GitHub Packages is the v11 release channel; every tag publishes the full set of Fallout.* + Nuke.* packages there.

The Nuke.* shim packages always go to GitHub Packages (per [#47](https://github.com/ChrisonSimtian/Fallout/issues/47) — those IDs are owned by the original NUKE maintainer on nuget.org). This is unchanged.

## Consequences

### Positive

- **Default safety.** A tagged release on `release/v11` can't accidentally publish to nuget.org. The opt-in flag is the explicit "this is stabilised enough" decision.
- **Three layers of nuget.org safety**: tag protection (admin only) + flag opt-in + env approval. Reflects the irreversibility of nuget.org publishes.
- **No CI restructuring beyond the flag.** The existing job shape, environment routing, and approval gates carry over. Small, targeted change.
- **Same pipeline serves v10.x maintenance and stabilised v11.** When `release/v10.x` branches get cut for maintenance lines, the same workflow handles them — just set the flag.

### Negative

- **Maintainer has to remember the flag.** For genuinely-stabilised releases this is a small friction. Mitigation: documented in `docs/branching-and-release.md` step-by-step.
- **GitHub Packages is now load-bearing for v11.** Beta consumers (and us) rely on it as the primary release channel. If GH Packages has an outage, v11 release publishing is blocked until it recovers. The `--skip-duplicate` idempotency means retries are cheap, but the dependency is real.
- **Documentation churn.** Three docs needed updating (`docs/agents/release-and-versioning.md`, `docs/branching-and-release.md`, this ADR) shortly after ADR-0001 landed.

### Neutral

- **ADR-0001 stays accurate** as the description of the release-branch model itself. The Tier 1 / Tier 2 / Tier 3 taxonomy still applies. This ADR documents the *routing policy* on top of that model.
- **`project_release_channels` agent memory** captures the steady-state design. A separate memory entry (`project_v11_off_nuget`) captures the v11-specific carve-out. Future v12/v13 may revisit.

## Alternatives considered

### A. Branch-aware hard-coding in the workflow

Conditional logic: `if ref == 'release/v11' skip nuget.org`. Hard-codes the policy per release branch.

**Rejected because:**

- Every new release branch (v10.x maintenance, v12 stabilisation) would need a YAML edit to register its routing. The flag-based approach is uniform across branches.
- "Stabilised v11" is a fuzzy boundary — at some point v11 starts going to nuget.org. Hard-coded ref checks force a YAML edit at that boundary; the flag lets the boundary be a per-release decision.

### B. version.json field

Per-branch field like `"publishToNugetOrg": true|false`. The workflow reads it.

**Rejected because:**

- Adds tooling (YAML reading version.json, decision logic). For a binary "should I publish or not" decision the input flag is sufficient.
- The decision is per-release, not per-branch (a v11 patch you trust enough for nuget.org vs one you don't). Per-branch config is the wrong granularity.

### C. Two separate workflows

`release.yml` for GitHub Packages only, `release-nugetorg.yml` for nuget.org. Maintainer picks which to invoke.

**Rejected because:**

- Two workflows duplicate test-and-pack work and double the YAML to maintain.
- The shared test-and-pack + artifact upload is what makes the multi-channel fan-out efficient. Splitting would lose that.

## References

- [ADR-0001: Release-branch model & multi-channel CD](0001-release-branch-model.md) — the parent decision.
- [RFC #267](https://github.com/ChrisonSimtian/Fallout/issues/267) — original design discussion.
- [docs/branching-and-release.md](../branching-and-release.md) — maintainer runbook (updated with the routine + stabilised release paths).
- [#47](https://github.com/ChrisonSimtian/Fallout/issues/47) — Nuke.* shims live on GitHub Packages permanently.

## Memory artifacts (AI agent context)

- `project_v11_off_nuget.md` — the v11-off-nuget direction as recorded in agent memory.
- `project_release_channels.md` — the broader channel taxonomy (steady-state model).
39 changes: 29 additions & 10 deletions docs/agents/release-and-versioning.md
Original file line number Diff line number Diff line change
Expand Up @@ -73,23 +73,42 @@ If you only discover the breaking nature mid-review, apply all relevant steps be

## Release pipeline

`.github/workflows/release.yml` is **tag-triggered**: pushing a `v*` tag on a `release/v*` branch fires the pipeline. The workflow validates the tag is reachable from a `release/v*` branch, then fans out a Test+Pack job to three parallel publish jobs, one per GitHub Environment / channel tier:
`.github/workflows/release.yml` is **tag-triggered**: pushing a `v*` tag on a `release/v*` branch fires the pipeline. The workflow validates the tag is reachable from a `release/v*` branch, then fans out a Test+Pack job to three parallel publish jobs:

| Job | Environment | Channel | What ships | Gating |
| Job | Environment | Fires on tag push? | What ships | Gating |
|---|---|---|---|---|
| `publish-nuget-org` | `nuget-org` | Tier 1 — production | `Fallout.*.nupkg` to https://api.nuget.org/v3/index.json | Approval-gated (maintainer reviewer) |
| `publish-github-packages` | `github-packages` | Tier 2 — bleeding edge | `Nuke.*.nupkg` transition shims to https://nuget.pkg.github.com/ChrisonSimtian/index.json | None |
| `publish-github-releases` | `github-releases` | Bundled artifacts | All `*.nupkg` attached to a GitHub Release on the tag, auto-generated notes | None |
| `publish-nuget-org` | `nuget-org` | **No — opt-in only** via `workflow_dispatch` flag | `Fallout.*.nupkg` to https://api.nuget.org/v3/index.json | Workflow flag + approval-gated env |
| `publish-github-packages` | `github-packages` | Yes | **All** `*.nupkg` (Fallout.* + Nuke.*) to https://nuget.pkg.github.com/ChrisonSimtian/index.json | None |
| `publish-github-releases` | `github-releases` | Yes | All `*.nupkg` attached to a GitHub Release on the tag, auto-generated notes | None |

`Nuke.*` shim packages are routed to GitHub Packages, not nuget.org — those package IDs are owned by the original NUKE maintainer on nuget.org (see [#47](https://github.com/ChrisonSimtian/Fallout/issues/47)).
### Why nuget.org is opt-in for v11

Each `dotnet nuget push` uses `--skip-duplicate`, so re-runs of a partial publish (one channel failed transiently) are idempotent on the channels that already succeeded.
For v11, **GitHub Packages is the de-facto release channel.** nuget.org is reserved for v10.x maintenance lines and a future "stabilised" v11. To publish Fallout.* to nuget.org you must run `workflow_dispatch` with `publish-to-nugetorg=true` — a conscious "this release is ready for nuget.org" switch. Tag pushes alone publish to GitHub Packages + GitHub Releases only.

**Tag protection.** `v*` tags are protected via a repository ruleset (rules: creation, deletion, update). Bypass actors: repo admins only. Non-admins cannot create release tags — combined with the env approval gate on `nuget-org`, this gates "who can fire a production release" at two layers.
Two layers of protection on the nuget.org path: the input flag opt-in, plus the `nuget-org` environment's required-reviewer rule.

**Fallback trigger: `workflow_dispatch`.** Manual runs accept an existing-tag input — used to re-run the publish fan-out after a transient API failure. The workflow checks out the specified tag and re-runs the publish chain. `--skip-duplicate` keeps the re-run safe.
### Nuke.* shims

**Channel philosophy** (per [RFC #267](https://github.com/ChrisonSimtian/Fallout/issues/267)): nuget.org is slow/stable/production; GitHub Packages is faster/beta/bleeding-edge; GitHub Releases bundles artifacts on the tag. A planned Tier 3 (Docker-based local NuGet server for pre-merge testing) is tracked in [#279](https://github.com/ChrisonSimtian/Fallout/issues/279).
`Nuke.*` transition-shim package IDs are owned by the original NUKE maintainer on nuget.org (see [#47](https://github.com/ChrisonSimtian/Fallout/issues/47)) — they're permanently routed to GitHub Packages, never nuget.org, regardless of the input flag.

### Re-runs

Each `dotnet nuget push` uses `--skip-duplicate`, so re-runs of a partial publish (one channel failed transiently) are idempotent on packages that already succeeded.

### Tag protection

`v*` tags are protected via a repository ruleset (rules: creation, deletion, update). Bypass actors: repo admins only. Combined with the workflow-dispatch flag and env approval, the nuget.org path has *three* layers (tag-creation + flag opt-in + env approval).

### `workflow_dispatch` inputs

- `tag` (required) — existing tag to (re-)release.
- `publish-to-nugetorg` (boolean, default `false`) — opt into the nuget.org publish job for this run.

Common use cases: re-running a transient-failed publish (`tag` only), or shipping a stabilised release to nuget.org (`tag` + `publish-to-nugetorg=true`).

### Channel philosophy

Per [RFC #267](https://github.com/ChrisonSimtian/Fallout/issues/267): nuget.org = production-grade & slow; GitHub Packages = faster cadence (currently the v11 release channel); GitHub Releases = bundled artifacts. A planned Tier 3 (Docker-based local NuGet server for pre-merge testing) shipped via [#279](https://github.com/ChrisonSimtian/Fallout/issues/279) — see `tests/integration/docker-compose.yml`.

`NUGET_API_KEY` is scoped to the `nuget-org` GitHub Environment (per [#273](https://github.com/ChrisonSimtian/Fallout/issues/273)) — only resolves in the gated job. Prefix reservation tracked in [#33](https://github.com/ChrisonSimtian/Fallout/issues/33).

Expand Down
Loading
Loading