From e85932a8ee894d5c351c2f3908b720be9d6604b0 Mon Sep 17 00:00:00 2001
From: Chrison Simtian
Date: Fri, 31 Jul 2026 10:07:27 +1200
Subject: [PATCH 01/10] rewrite README
---
README.md | 90 +++++++++++++++++++++++--------------------------------
1 file changed, 37 insertions(+), 53 deletions(-)
diff --git a/README.md b/README.md
index f196d40f..9e9714ac 100644
--- a/README.md
+++ b/README.md
@@ -6,27 +6,27 @@
π Documentation: docs.fallout.build
-> π¦ **Fallout is the successor to NUKE.** [Migrating from NUKE β](docs/migration/from-nuke.md)
-
-# Fallout
-
-> Build automation for C#/.NET β the hard-fork successor to NUKE.
-
-[](docs/badge.md)
-[](https://docs.fallout.build/)
-[](https://github.com/Fallout-build/Fallout/actions/workflows/publish-packages-preview.yml)
-[](https://www.nuget.org/packages/Fallout.Common)
[](https://www.nuget.org/packages/Fallout.Common)
-[](https://github.com/Fallout-build/Fallout/releases/latest)
[](https://github.com/Fallout-build/Fallout/commits/main)
-[](https://dot.net)
-[](LICENSE)
[](https://github.com/Fallout-build/Fallout/issues)
[](https://github.com/Fallout-build/Fallout/pulls)
-[](https://github.com/sponsors/ChrisonSimtian)
+[](https://github.com/sponsors/Fallout-Build)
+[](https://github.com/Fallout-build/Fallout/actions/workflows/publish-packages-preview.yml)
+[](https://github.com/Fallout-build/Fallout/releases/latest)
+[](https://www.nuget.org/packages/Fallout.Common)
+[](https://dot.net)
+[](docs/badge.md)
+[](https://docs.fallout.build/)
+[](LICENSE)
+
+# Fallout
+
+> Build automation for C#/.NET β survived the NUKE.
## Based on NUKE
+> π¦ **Fallout is the successor to NUKE.** [Migrating from NUKE β](docs/migration/from-nuke.md)
+
Fallout is the successor to **[NUKE](https://github.com/nuke-build/nuke)**, originally created by **Matthias Koch** ([matkoch](https://github.com/matkoch)) and many contributors. Fallout continues NUKE's mission as a C#-first build automation framework for .NET β under new maintenance, with an enterprise-CI/CD focus.
The original NUKE code is preserved here under the MIT License with attribution. Major version 10.x was the last NUKE release; everything from this fork forward carries the Fallout identity.
@@ -49,61 +49,35 @@ dotnet tool install -g Fallout.GlobalTool
The CLI installs as `fallout`. Verify with `fallout --help`.
-> [!NOTE]
-> **Coming from NUKE's `10.x` tool?** Nothing to do β `Fallout.GlobalTool` is the same package id you already have pinned, so `dotnet tool update` just works. The only exception is the short-lived `Fallout.GlobalTools` (plural) id: if you installed `10.4.0-rc.4` from it, uninstall it and reinstall from `Fallout.GlobalTool` so you don't have two tools claiming the `fallout` command.
->
-> ```sh
-> dotnet tool uninstall -g Fallout.GlobalTools
-> ```
-
For per-repo manifest pinning (`.config/dotnet-tools.json`), project setup, and shell completion, see the [Installation guide on docs.fallout.build](https://docs.fallout.build/getting-started/installation).
-> [!NOTE]
-> **Channels.** Stable releases ship on **calendar versions** (`YYYY.MINOR.PATCH`, e.g. `2026.1.3`; the major is the year) from the `release/YYYY` production line β published to GitHub Packages, with nuget.org publishing opt-in per release. The `main` integration trunk publishes a faster `β¦-preview.β¦` prerelease to **GitHub Packages only**; opt in by adding the GitHub Packages feed and a prerelease version range. The legacy NUKE `10.x` line (`support/v10`) stays on semver and receives security/critical fixes only. See [ADR-0004](docs/adr/0004-calendar-versioning-and-dual-pace-channels.md) (as amended by [ADR-0008](docs/adr/0008-collapse-experimental-into-main.md)) and [docs/branching-and-release.md](docs/branching-and-release.md) for the full model.
-
## Table of Contents
-- [Elevator Pitch](#elevator-pitch)
+- [Project Description](#project-description)
- [Build Status](#build-status)
-- [Activity](#activity)
- [Contribute](#contribute)
- [Sponsorship](#sponsorship)
+- [Activity](#activity)
-## Elevator Pitch
-
-Solid and scalable CI/CD pipelines are an essential pillar for being competitive and creating a great product. But why are most of us a little afraid of touching YAML files and don't even dare to look at build scripts? Much of this is because C# developers are spoiled with a great language and smart IDEs, and they don't like missing their buddy for code-completion, ease of debugging, refactorings, and code formatting.
+## Project Description
-Fallout (NUKE's successor) brings your build automation to an even level with every other .NET project. How? It's a regular console application allowing all the OOP goodness! Besides, it solves many common problems in build automation, like parameter injection, path separator abstraction, access to solution and project models, and build step sharing across repositories. Fallout can also generate CI/CD configurations (YAML, etc.) that automatically parallelize build steps on multiple agents to optimize throughput!
+We all have to deal with CI/CD on our day-to-day, but for some reason those pipelines come with their own language, their own terms, their own platform and every single one works ever so slightly different. And even worse, half of them need to be tested in production.
+The promise of Fallout, just like its predecessor NUKE, is to deliver a C#-based pipeline system that is not just local-first (i.e. you can run it on your machine and it does exactly the same thing as it would in the CI. No magic, no emulators, no batteries needed). It also allows you to stay in your beloved dotnet language ecosystem! We aim to provide a highly flexible CI/CD pipeline platform that lets you test, run and expand your builds and deployments the way YOU want.
## Build Status
-CI runs on every PR targeting `main`, `release/*`, or `support/*` on `build.yml` (Linux) β its `ubuntu-latest` job is the only required status check. Cross-platform Test+Pack (Windows + macOS) is gated to release intent β PRs into `release/*` / `support/*` and `v*` tag pushes β via `build-cross-platform.yml`; it does not run on routine `main` work. A `β¦-preview` prerelease is published to **GitHub Packages** under the reserved `Fallout.*` prefix on every push to `main`. **Stable** releases fire from `release/YYYY` tags via `.github/workflows/publish-packages-release.yml` (GitHub Packages + GitHub Releases by default; nuget.org opt-in per release). Docs-only PRs are served by a no-op companion workflow (`build-skip`) so branch protection is satisfied without spending CI minutes on a real build.
-
| Workflow | Status | Trigger |
|---|---|---|
| [`build`](.github/workflows/build.yml) | [](https://github.com/Fallout-build/Fallout/actions/workflows/build.yml) | PR to `main` / `release/*` / `support/*` (code paths) β job `ubuntu-latest` is the **required check** |
-| [`build-cross-platform`](.github/workflows/build-cross-platform.yml) | [](https://github.com/Fallout-build/Fallout/actions/workflows/build-cross-platform.yml) | PR to `release/*` / `support/*` or `v*` tag push β Windows + macOS (release intent) |
| [`publish-packages-preview`](.github/workflows/publish-packages-preview.yml) | [](https://github.com/Fallout-build/Fallout/actions/workflows/publish-packages-preview.yml) | push to `main` β `β¦-preview` prerelease to GitHub Packages |
| [`publish-packages-release`](.github/workflows/publish-packages-release.yml) | [](https://github.com/Fallout-build/Fallout/actions/workflows/publish-packages-release.yml) | tag push on `release/YYYY` (stable) or `support/*` (legacy/retired) β nuget.org opt-in |
Multi-provider CI support (Azure Pipelines, GitLab, TeamCity, AppVeyor) was removed during the takeover and is being revived demand-driven β see [#8](https://github.com/Fallout-build/Fallout/issues/8).
-## Activity
-
-### Commits, issues, PRs (rolling 30 days)
-
-
-
-Generated by [Repobeats](https://repobeats.axiom.co).
-
-### Stars over time
-
-[](https://repostars.dev/?repos=Fallout-build%2FFallout&theme=terminal)
-
-Generated by [repostars.dev](https://www.repostars.dev/). Auto-updates as new stargazers arrive.
-
## Contribute
+Want to contribute code, docs, or triage? Start with [CONTRIBUTING.md](CONTRIBUTING.md).
+
Two things help the project and take about a minute:
- **Star the repo.** It is how most people find Fallout.
@@ -118,16 +92,26 @@ Two things help the project and take about a minute:
More styles and wordings, plus the logo source, are in [docs/badge.md](docs/badge.md).
-Want to contribute code, docs, or triage? Start with [CONTRIBUTING.md](CONTRIBUTING.md).
-
## Sponsorship
-Fallout is volunteer-run, and we want to be transparent about what running the project costs β see [`costs.md`](https://github.com/Fallout-build/.github/blob/main/costs.md) for the full list. It lives with the organisation's other shared project files, since the spend covers every repository rather than this one.
-
-If you or your organisation would like to help offset those costs, use the sponsor button on any Fallout repository, or open an issue and we'll work out the details.
+Fallout is volunteer-run. We happily accept sponsorship via the sponsor button on any Fallout repository, or if you want to financially support us through other channels, please reach out to us via [Email](mailto:funding@fallout.build). We will try our best to be as transparent as possible about the running [`Costs`](https://github.com/Fallout-build/.github/blob/main/costs.md) of this project and where your contributions go β they live with the organisation's other shared files, since the spend covers every repository rather than this one. We currently cant offer you a not-for-profit donation statement, as we're not set up as a Not-For-Profit organisation (yet).
## Credits
-- [Matthias Koch](https://github.com/matkoch) and the [NUKE contributors](https://github.com/nuke-build/nuke/graphs/contributors) β for creating and maintaining NUKE through version 10.x.
+- [Matthias Koch](https://github.com/matkoch) and the [NUKE contributors](https://github.com/nuke-build/nuke/graphs/contributors) β for creating and maintaining NUKE through version `10.1.0`.
If you maintained or contributed to NUKE and want to be credited differently here, please open an issue.
+
+## Activity
+
+### Commits, issues, PRs (rolling 30 days)
+
+
+
+Generated by [Repobeats](https://repobeats.axiom.co).
+
+### Stars over time
+
+[](https://repostars.dev/?repos=Fallout-build%2FFallout&theme=terminal)
+
+Generated by [repostars.dev](https://www.repostars.dev/). Auto-updates as new stargazers arrive.
From a520b064d463792c0c22d42d937f6dee7bc33a64 Mon Sep 17 00:00:00 2001
From: Chrison Simtian
Date: Fri, 31 Jul 2026 10:22:34 +1200
Subject: [PATCH 02/10] rework branching and release documentation for clarity
and conciseness
---
docs/branching-and-release.md | 291 ++--------------------------------
1 file changed, 15 insertions(+), 276 deletions(-)
diff --git a/docs/branching-and-release.md b/docs/branching-and-release.md
index a685962e..aa391096 100644
--- a/docs/branching-and-release.md
+++ b/docs/branching-and-release.md
@@ -1,284 +1,23 @@
# Branching and release flow
-Maintainer reference for how Fallout branches, ships releases, hotfixes older lines, and uses GitHub Environments to gate publishes. Model defined by [ADR-0004](adr/0004-calendar-versioning-and-dual-pace-channels.md) (calendar versioning + dual-pace channels), amending [ADR-0001](adr/0001-release-branch-model.md) / [milestone #13](https://github.com/Fallout-build/Fallout/milestone/13) / [RFC #267](https://github.com/Fallout-build/Fallout/issues/267). The `experimental` branch and its `-alpha` channel have since been collapsed into `main` ([ADR-0008](adr/0008-collapse-experimental-into-main.md), channel ladder superseded) β `main` is now the sole prerelease lane.
+We aim to follow [Gitlab Flow](https://about.gitlab.com/topics/version-control/what-is-gitlab-flow/), a lightweight Gitflow alternative.
+What does that mean for you as a contributor or maintainer of this project?
-> **Audience.** Repository maintainers cutting releases or hotfixing older lines. Contributors filing PRs against `main` don't need to read this β see [CONTRIBUTING.md](https://github.com/Fallout-build/Fallout/blob/main/CONTRIBUTING.md) instead. AI coding tools should read both this file and [docs/agents/release-and-versioning.md](agents/release-and-versioning.md).
+## How to contribute code
-## Branches at a glance
+1. You develop on a local fork
+2. You raise a PR once your work is ready for review
+3. Target `main` on the Fallout-Upstream
+4. Your code gets merged
-A maturity ladder feeding the production line (amended [ADR-0004](adr/0004-calendar-versioning-and-dual-pace-channels.md), 2026-05-30; the `experimental` rung collapsed into `main` per [ADR-0008](adr/0008-collapse-experimental-into-main.md)):
+## How to publish a new release
-| Branch | Purpose | Lifetime | Protected | Source of releases? |
-|---|---|---|---|---|
-| `main` | **Integration trunk + sole prerelease lane (`-preview` channel).** Default branch. Both deliberate improvements / bug fixes **and** faster / AI-assisted work land here. Per-commit `β¦-preview` prereleases to GitHub Packages. **Never nuget.org.** Breaking work accumulates here gated behind `[Experimental("FALLOUT0xx")]` (or on a short-lived topic branch off `main`) for the yearly major. | Long-lived | Yes | **Preview only** (GitHub Packages, no nuget.org / no GH Release) |
-| `release/YYYY` | **Production line** for the calendar year (e.g. `release/2026`), **cut from `main` on demand at the first release of the year, not preemptively** ([ADR-0007](adr/0007-cut-release-branch-on-demand.md)). `-rc.N` β GA. Non-breaking minors/patches only after the cut. | Cut on demand; long-lived once cut | Yes | **Yes** β tags pushed here fire the full release pipeline (nuget.org opt-in) |
-| `release/vMAJOR.MINOR` | **Production line for a semver minor** (e.g. `release/v10.4`) β the shape in use while the line still ships `10.x`, before the CalVer major cut. Same rules and pipeline as `release/YYYY`; both are matched by `publicReleaseRefSpec` and by `validate-ref`. | Cut on demand; long-lived once cut | Yes | **Yes** β same pipeline |
-| `support/v10` (+ `hotfix/v10.1`, `hotfix/v10.2`) | **Legacy** semver `10.x` maintenance line β security/critical fixes only. (Renamed from `release/v10`.) | Long-lived | Yes | Yes β tags fire the pipeline (nuget.org opt-in) |
-| `support/YYYY` | **Retired** year production line (e.g. `support/2026` once 2027 supersedes it). Security/critical fixes only. | Long-lived | Yes | Yes β tags fire the pipeline (nuget.org opt-in) |
-| `release/v11` | **Retired and deleted** β nothing clean shipped; work re-homed onto `2026`. Branch removed per [ADR-0007](adr/0007-cut-release-branch-on-demand.md) Β§6 (no unique history; dead branches are deletable, tags are the durable markers). | Deleted | β | No |
-| `feature/`, `bugfix/`, `chore/`, `docs/`, `pr/-` | Working branches | Short-lived; PR-and-merge then deleted | No | No |
+Sometimes it becomes necessary to create a stabilisation branch to make sure we iron out the worst bugs before pushing a release.
+For this purpose Gitlab Flow allows us to create branches, i.e. `release/v10.4`
+> [!Note] While a release branch exists, it becomes necessary to raise some PRs against `release/v10.4` and **then** upmerge those changes against `main` as well
-This *is* gitflow with the project's vocabulary: `main` β the integration trunk / `develop`, `release/YYYY` β `release/*` (long-lived per year), `support/*` β legacy/retired lines. The one deviation: **`main` is not the production/nuget.org line** β `release/YYYY` + `support/*` are. `main` is a `-preview` test channel that production is cut from.
+Once we feel comfortable with our release, we can `git tag` our release with the appropiate version, which triggers our CI to run the publish release pipeline.
-`develop` (literal) and `master` are not used. **Breaking changes land on `main`** β gated behind the `[Experimental("FALLOUT0xx")]` attribute, or, when they can't be gated, on a short-lived topic branch off `main` β and are batched to the yearly major cut; the production-cut review is the backstop. A breaking-change PR targets `main`, never a `release/YYYY` production train. Stabilised non-breaking work is promoted **forward-only** `main β release/YYYY`. A stable-urgent fix lands on the production branch and is **forward-ported to `main`** so the trunk never regresses β see the [promotion + hotfix flow](#promotion-and-hotfixing) below.
-
-## Channel taxonomy
-
-### Lines live right now
-
-Keep this block current β the examples further down use these values.
-
-| Line | Branch | Ships | Latest |
-|---|---|---|---|
-| Preview | `main` | `10.5.0-preview..g` β GitHub Packages, per commit | rolling |
-| Production | `release/v10.4` | `10.4.0-rc.N` β GitHub Packages + GH Release; nuget.org opt-in | `v10.4.0-rc.5` |
-| Legacy | `support/v10` | `10.x` security/critical only | `10.3.47` |
-
-`main` is deliberately **not** in `publicReleaseRefSpec`, which is why its previews carry the `.g` suffix β they're non-public builds by design. Production lines are listed there, so their packages are clean (see [ADR-0004](adr/0004-calendar-versioning-and-dual-pace-channels.md) for the CalVer target; the current line is still `10.x`).
-
-### Channels
-
-Releases fire to multiple channels, each with its own GitHub Environment:
-
-**GitHub Packages = the test/preview channel; nuget.org = production.** The version ladder orders cleanly under SemVer: `β¦-preview.N` < `β¦-rc.N` < `β¦` (GA) β the `-alpha` rung was retired with the `experimental` branch ([ADR-0008](adr/0008-collapse-experimental-into-main.md)).
-
-| Channel | Built from | Cadence | Gating | Version shape |
-|---|---|---|---|---|
-| **preview** β `github-packages` env | `main` | Per-commit | None | `10.5.0-preview..g` |
-| **stable** β `nuget-org` env | `release/*` tags | Slow, deliberate | **Flag opt-in + approval-gated** | `10.4.0-rc.N` today; `YYYY.M.P` after the CalVer cut |
-| **stable/legacy** β `github-packages` env | `release/*`, `support/*` tags | Every tag | None | Same as the tag |
-| **legacy** β `nuget-org` env | `support/v10`, `support/YYYY` tags | Security/critical only | **Flag opt-in + approval-gated** | `10.x` / `YYYY.x` |
-| `github-releases` env (bundled) | `release/*`, `support/*` tags | Same tag as the package publish | None | Same as the tag |
-| Docker local NuGet server | Per-PR / per-commit | None (local) | PR-derived | Available via `tests/integration/docker-compose.yml` |
-
-**Defaults:** `main` (preview) publishes to GitHub Packages only β **never nuget.org, never a GH Release**. `publish-packages-preview.yml` (main β `-preview`) is the only continuous publisher; the former `experimental.yml` workflow has been deleted ([ADR-0008](adr/0008-collapse-experimental-into-main.md)). Production tag pushes (`release/YYYY`, `support/*`) publish to GitHub Packages + GitHub Releases. nuget.org is **always opt-in** via the `workflow_dispatch` `publish-to-nugetorg` flag β used when a `release/YYYY` is stabilised enough for the broader consumer audience, or for a `support/v10` security patch. See [`project_release_channels` in agent memory](https://github.com/Fallout-build/Fallout/issues/267#issuecomment-4570408325) and [ADR-0004](adr/0004-calendar-versioning-and-dual-pace-channels.md).
-
-## Cutting a release
-
-### Prerelease numbers on a release branch are a manual counter
-
-On a release branch, `version.json`'s `version` pins the prerelease number literally β `10.4.0-rc.4`, not `10.4.0-rc.{height}`. **Bump it in a PR before you tag.** Two consequences:
-
-- Every commit on the branch reports the same version until you bump, so the number tracks *release intent* rather than however many commits a promotion happened to carry. (`{height}` sent `rc.3` straight to `rc.23` on the first 19-commit promotion.)
-- Tagging twice without bumping republishes an existing version. `dotnet nuget push --skip-duplicate` swallows that silently, so the packages simply won't update β **check the number first**.
-
-`main` keeps `{height}` (`10.5.0-preview.{height}`): per-commit previews want a value that always moves on its own.
-
-### Routine stable release (GitHub Packages only)
-
-The default path. Pushing a tag to a production branch publishes to GitHub Packages + GitHub Releases. nuget.org is **not** touched. Git tags keep the `v` prefix β `v10.4.0-rc.4`, `v2026.1.3` β so the `v*` tag-protection ruleset and `validate-ref` apply; the package version core drops it (`10.4.0-rc.4`).
-
-Examples below use the live line, `release/v10.4`. A CalVer `release/YYYY` cut works identically β substitute the branch and tag.
-
-```bash
-# 1. Make sure your local release branch is up to date
-git fetch
-git switch release/v10.4
-git pull --ff-only
-
-# 2. Bump the rc number in version.json via a PR if you haven't (see above),
-# then verify what NB.GV will compute. Note PublicRelease=1: a plain local
-# run reports a .g suffix because your checked-out branch is only a
-# public ref in CI's eyes.
-PublicRelease=1 dotnet nbgv get-version -v NuGetPackageVersion # e.g. 10.4.0-rc.4
-
-# 3. Create the tag + GitHub Release in one step.
-# --notes-start-tag is load-bearing: see "Release notes" below.
-# Add --prerelease for an rc.
-gh release create v10.4.0-rc.4 \
- --target release/v10.4 \
- --title "v10.4.0-rc.4" \
- --prerelease \
- --generate-notes \
- --notes-start-tag 10.3.47 # the last GA, NOT the previous rc
-```
-
-### Release notes
-
-**Always use `--generate-notes`.** It groups merged PRs by the label taxonomy in [`.github/release.yml`](https://github.com/Fallout-build/Fallout/blob/main/.github/release.yml) and credits every contributor, including a "New Contributors" section. Don't hand-write notes.
-
-**Set `--notes-start-tag` to the last GA tag, not the previous prerelease.** Left to itself, `gh` picks the most recent tag β so an rc diffs against the rc before it and the notes collapse to whatever landed in between. Anchoring on the last GA (`10.3.47` for the 10.4 line) makes every rc's notes show the full set of changes since the last real release, which is what someone evaluating an rc wants to read.
-
-One known gap: work promoted onto a release branch by cherry-pick gets **new commit SHAs**, so GitHub can't map it back to the PRs it came from and it shows up as the single promotion PR instead of the individual ones. The pre-cut history (inherited when the branch was cut) maps fine. If a promotion carried work worth itemising, add a short summary paragraph above the generated section rather than replacing it.
-
-That tag push triggers `.github/workflows/publish-packages-release.yml`:
-
-1. **`validate-ref`** confirms the tag points at a commit reachable from a production branch (`release/YYYY`, `release/vMAJOR.MINOR`, or `support/*`).
-2. **`test-and-pack`** runs `dotnet fallout Test Pack`, uploads `output/packages/*.nupkg` as an artifact.
-3. Three parallel publish jobs consume the artifact:
- - `publish-nuget-org` β **skipped** (not opt-in by default)
- - `publish-github-packages` β pushes **all** `*.nupkg` (Fallout.* + Nuke.*) to GitHub Packages
- - `publish-github-releases` β attaches all `*.nupkg` to the GitHub Release page
-
-### Stabilised release (nuget.org publish)
-
-When a release is stabilised enough for nuget.org, or for cutting a `support/v10` legacy security patch, use `workflow_dispatch` with the opt-in flag:
-
-```bash
-# Option A: via gh CLI
-gh workflow run publish-packages-release.yml \
- --ref release/v10.4 \
- -f tag=v10.4.0-rc.4 \
- -f publish-to-nugetorg=true
-
-# Option B: via Actions UI β publish-packages-release β "Run workflow" β set publish-to-nugetorg to true
-```
-
-> **`--ref` is not optional.** `workflow_dispatch` takes the **workflow definition** from the ref you dispatch against, while `-f tag=` only controls which source gets checked out and packed. Dispatch against the default branch and you run `main`'s copy of the pipeline β which will differ from the release branch's whenever a pipeline fix hasn't been forward-ported yet, and will happily push the resulting packages to nuget.org. Always pass the production branch.
-
-The workflow:
-
-1. Skips `validate-ref` (workflow_dispatch doesn't auto-validate the ref; you took the action consciously).
-2. Re-runs `test-and-pack` against the named tag.
-3. **`publish-nuget-org` fires** β pauses for approval at the `nuget-org` env gate (notification + entry on the run page; click "Review deployments" β check `nuget-org` β "Approve and deploy"). Then pushes Fallout.* to nuget.org.
-4. `publish-github-packages` re-runs idempotently (`--skip-duplicate` skips what's already there).
-5. `publish-github-releases` re-runs idempotently (uses `--clobber` for asset replacement if the GH Release already exists).
-
-Two layers of safety on the nuget.org path: the flag opt-in + the env approval. You can also test the wiring without burning a release β set the flag, get the approval prompt, then cancel without approving.
-
-**A green run is not proof anything published.** Every publish job is conditional, so a misconfigured condition skips it while the run still reports success β this happened to all three jobs on the `workflow_dispatch` path until 2026-07-26. After any release, check the jobs actually ran and then confirm the packages resolve:
-
-```bash
-# Did the publish jobs run, or silently skip?
-gh api repos/Fallout-build/Fallout/actions/runs//jobs \
- --jq '.jobs[] | "\(.name): \(.conclusion)"'
-
-# Is the version really on nuget.org? (expect the version listed)
-curl -s https://api.nuget.org/v3-flatcontainer/fallout.common/index.json | jq '.versions[-3:]'
-```
-
-Allow a minute or two for nuget.org to index β a package can be pushed successfully and not yet appear, especially a brand-new package ID.
-
-### If a publish fails partway through
-
-Each `dotnet nuget push` uses `--skip-duplicate`. Re-running a publish job is idempotent on packages already pushed. For a transient failure mid-publish:
-
-```bash
-# Routine re-run β leave publish-to-nugetorg false
-gh workflow run publish-packages-release.yml --ref release/v10.4 -f tag=v10.4.0-rc.4
-
-# Stabilised re-run β include the flag if you want to retry the nuget.org push
-gh workflow run publish-packages-release.yml --ref release/v10.4 -f tag=v10.4.0-rc.4 -f publish-to-nugetorg=true
-```
-
-## Promotion and hotfixing
-
-The ladder flows **forward-only**: `main β release/YYYY`. One routine promotion direction plus the legacy case.
-
-### Where work lands
-
-All work β deliberate improvements, bug fixes, and faster / AI-assisted changes alike β lands directly on `main`; there is no separate fast lane any more ([ADR-0008](adr/0008-collapse-experimental-into-main.md)). Breaking work also lands on `main`, gated behind `[Experimental("FALLOUT0xx")]` (or on a short-lived topic branch off `main` when it can't be gated), and waits for the yearly cut β it is **not** promoted to a `release/YYYY` mid-year.
-
-### Promoting `main β release/YYYY` (a stable patch/minor)
-
-A stabilised non-breaking change on `main` is promoted to the production line, then tagged.
-
-```bash
-git fetch
-git switch -c promote-XXXX-to-v10.4 release/v10.4
-git cherry-pick [ β¦]
-git push origin HEAD
-gh pr create --base release/v10.4 ... # rigorous review tier
-# once merged:
-gh release create v10.4.0-rc.5 --target release/v10.4 --prerelease \
- --generate-notes --notes-start-tag 10.3.47
-```
-
-### Forward-porting a stable-urgent fix
-
-If a fix must land on the production line first (prod-down), land it on `release/v10.4`, then **forward-port** to `main` so the trunk never regresses:
-
-```bash
-git switch -c forward-port-XXXX main
-git cherry-pick
-git push origin HEAD
-gh pr create --base main ...
-```
-
-### Legacy `support/v10`
-
-A `support/v10` security/critical fix that doesn't apply to the current line (the code has moved on) lands **directly** on `support/v10` (or the relevant `hotfix/v10.x`) via PR β the expected path for a maintenance line, not the exception. Such a release is the nuget.org case (use the opt-in flag). The same applies to a retired `support/YYYY` line.
-
-> Even one-commit cherry-picks go through a PR β branch protection blocks direct pushes and requires the `ubuntu-latest` status check on every protected branch.
-
-## Cutting a new year (the yearly major)
-
-At the yearly major cut, the outgoing year's production line is retired to `support/YYYY` and a new `release/YYYY` is cut from `main`. The breaking work accumulated on `main` (gated behind `[Experimental("FALLOUT0xx")]`, plus any short-lived topic branches held for the cut) becomes the new year's major.
-
-```bash
-# 1. Retire the outgoing production line: rename release/2026 β support/2026
-# (GitHub Settings β Branches β rename, or via API). It keeps taking
-# security/critical fixes only from here on.
-
-# 2. Cut the new production line from main
-git fetch
-git switch main
-git pull --ff-only
-git switch -c release/2027 main
-git push -u origin release/2027
-
-# 3. Nothing to do β branch protection is already in force. The "Protect
-# release/** production lines" ruleset targets refs/heads/release/**, so a
-# new release branch is protected the moment it is pushed. See
-# "Branch protection" below.
-
-# 4. On release/2027 (the branch itself), set version.json "version": "2027.0".
-# publicReleaseRefSpec already matches "^refs/heads/release/\\d{4}$" β confirm
-# it resolves so NB.GV produces clean versions, not git-sha-suffixed.
-# Commit via PR targeting release/2027.
-
-# 5. Roll the preview lane forward so its prereleases sort above the new production
-# line. The accumulated breaking work is already on main (gated behind
-# [Experimental] / topic branches merged in); bump the core:
-# - main/version.json β "2027.1.0-preview.{height}"
-```
-
-### Step 4 β why on `release/2027`, not `main`
-
-`publicReleaseRefSpec` is per-branch. The CalVer ref pattern (`^refs/heads/release/\d{4}$`) matches `release/2027` automatically, but the `"version"` field is per-branch: `release/2027` pins `"2027.0"` (a public ref β clean versions) while `main` moves on to the next preview target. This keeps the production line's number stable and avoids a patch-height collision with the preview lane.
-
-## Deprecating a `support/*` line
-
-Once a `support/YYYY` or `support/v10` line hits end-of-life:
-
-1. Final patch release.
-2. Announce EoL in the README + CHANGELOG.
-3. Leave the branch in place β don't delete it. Future archaeology + historical hotfix-on-demand should remain possible (this is why `release/v11` stays around despite being retired).
-4. Optionally apply a more restrictive protection profile (e.g. require admin approval on every merge) to make accidental tags less likely.
-
-Branches are cheap. Deletion is destructive. Default to keeping.
-
-## Branch protection
-
-Production branches are protected by the **"Protect release/\*\* production lines"** ruleset ([ruleset 19766406](https://github.com/Fallout-build/Fallout/rules/19766406)), which targets `refs/heads/release/**`. Because it matches on a pattern, every release line β `release/v10.4` today, a `release/2027` CalVer cut later β is protected the moment the branch exists. There is no per-branch step to remember.
-
-It mirrors `main`'s profile: no deletion, no force-push, linear history required, PRs required with CODEOWNERS review and conversation resolution, and the `ubuntu-latest` status check. Repo admins (`RepositoryRole 5`) bypass, matching the tag ruleset.
-
-The payload lives at [`.github/release-branch-ruleset.json`](https://github.com/Fallout-build/Fallout/blob/main/.github/release-branch-ruleset.json) so the config is reviewable rather than only visible in repo settings. To re-apply after editing it:
-
-```bash
-# Update the existing ruleset in place (preferred β keeps the ID stable)
-gh api -X PUT repos/Fallout-build/Fallout/rulesets/19766406 \
- --input .github/release-branch-ruleset.json
-
-# Verify which rules actually bind to a branch
-gh api repos/Fallout-build/Fallout/rules/branches/release%2Fv10.4 --jq '[.[].type]'
-```
-
-`support/*` lines are **not** covered by this ruleset β they carry their own classic per-branch protection, applied when the line is created.
-
-> Historical note: `release/v10.4` ran unprotected from its cut until 2026-07-26, because the on-demand cut ([ADR-0007](adr/0007-cut-release-branch-on-demand.md)) had no protection step attached. The pattern-based ruleset exists so that can't recur.
-
-## Tag protection
-
-A repository ruleset blocks creation/deletion/update of tags matching `v*` for non-admins ([ruleset 17017817](https://github.com/Fallout-build/Fallout/rules/17017817)). Bypass actors: repo admins (`RepositoryRole 5`). Combined with the `nuget-org` env approval gate, that's two layers of "who can fire a production release."
-
-## See also
-
-- [docs/agents/release-and-versioning.md](agents/release-and-versioning.md) β PR-creation flow, semver policy, release pipeline reference, branch protection settings.
-- [docs/adr/0004-calendar-versioning-and-dual-pace-channels.md](adr/0004-calendar-versioning-and-dual-pace-channels.md) β the versioning + channel decision (channel ladder superseded by ADR-0008).
-- [docs/adr/0008-collapse-experimental-into-main.md](adr/0008-collapse-experimental-into-main.md) β collapses the `experimental` branch and its `-alpha` channel into `main`.
-- [docs/adr/0001-release-branch-model.md](adr/0001-release-branch-model.md) β the release-branch + multi-channel CD model (versioning amended by 0004).
-- [milestone #13](https://github.com/Fallout-build/Fallout/milestone/13) β full work-breakdown of how this shape was implemented.
-- [RFC #267](https://github.com/Fallout-build/Fallout/issues/267) β original design discussion.
-- [CONTRIBUTING.md](https://github.com/Fallout-build/Fallout/blob/main/CONTRIBUTING.md) β contributor-facing flow.
+- TODO: put in the correct references here
+- TODO: Mermaid diagram showing the branches and maybe a few examples of how to merge
+- TODO: cli commands examples for release candidate and actual release
From 8180a11b53c228b01fb2b6d551b67981c329c8bb Mon Sep 17 00:00:00 2001
From: Chrison Simtian
Date: Fri, 31 Jul 2026 15:45:29 +1200
Subject: [PATCH 03/10] branching strategy and how to do a release
---
docs/branching-and-release.md | 108 ++++++++++++++++++++++++++++++++--
1 file changed, 104 insertions(+), 4 deletions(-)
diff --git a/docs/branching-and-release.md b/docs/branching-and-release.md
index aa391096..8b30cf22 100644
--- a/docs/branching-and-release.md
+++ b/docs/branching-and-release.md
@@ -10,14 +10,114 @@ What does that mean for you as a contributor or maintainer of this project?
3. Target `main` on the Fallout-Upstream
4. Your code gets merged
+Every push to `main` triggers a pre-release on Github, including publishing nuget packages to Github (but not Nuget.org!).
+This is a cheap way to get our hands on pre-release packages without the cost of publishing anything to official Package Repositories.
+
## How to publish a new release
Sometimes it becomes necessary to create a stabilisation branch to make sure we iron out the worst bugs before pushing a release.
-For this purpose Gitlab Flow allows us to create branches, i.e. `release/v10.4`
-> [!Note] While a release branch exists, it becomes necessary to raise some PRs against `release/v10.4` and **then** upmerge those changes against `main` as well
+For this purpose Gitlab Flow allows us to create branches, i.e. `release/v1.0`
+> [!NOTE]
+> While a release branch exists, it becomes necessary to raise some PRs against `release/v1.0` and **then** upmerge those changes against `main` as well
+
+> [!INFO]
+> Please make sure to follow our naming pattern for release branches
+
+TODO: Create a documnent describing our naming patterns etc. in detail. Can probably sit with AI instructions.
+
+```mermaid
+flowchart TD
+ PR(["PR merged into main"]) --> PRE["publish-packages-preview"]
+ PRE -->|"env: github-packages"| PREOUT[("GitHub Packages
1.1.0-preview.42.g9f3c1a")]
+
+ TAG(["git tag v1.0.0
pushed on release/v1.0"]) --> VAL{"validate-ref
is the tag on a
release/* or support/* branch?"}
+ VAL -->|no| STOP["run fails β nothing published"]
+ VAL -->|yes| PACK["test + pack
dotnet fallout Test Pack"]
+ PACK --> ART[["artifact: output/packages/*.nupkg"]]
+
+ ART --> JGP["publish β GitHub Packages"]
+ ART --> JGR["publish β GitHub Releases"]
+ ART -.-> JNO["publish β nuget.org"]
+
+ JGP -->|"env: github-packages"| OGP[("GitHub Packages
every *.nupkg, incl. Nuke.* shims")]
+ JGR -->|"env: github-releases"| OGR[("Release page for v1.0.0
nupkgs attached")]
+ JNO -->|"env: nuget-org
+ manual approval"| ONO[("nuget.org
Fallout.* only")]
+
+ OPTIN["opt-in only:
workflow_dispatch with
publish-to-nugetorg=true"] -.-> JNO
+
+ classDef optional stroke-dasharray: 5 5
+ class JNO,ONO,OPTIN optional
+```
+
+### Upmerge (Preferred)
+
+```mermaid
+gitGraph
+ commit id: "β¦"
+ branch feature/my-contribution
+ commit id: "work"
+ checkout main
+ merge feature/my-contribution
+ branch release/v1.0
+ commit id: "pin version to 1.0" tag: "v1.0.0-rc.1"
+ checkout main
+ commit id: "unrelated feature"
+ checkout release/v1.0
+ branch bugfix/crash-on-startup
+ commit id: "fix the crash"
+ checkout release/v1.0
+ merge bugfix/crash-on-startup tag: "v1.0.0-rc.2"
+ commit id: "release notes" tag: "v1.0.0"
+ checkout main
+ merge release/v1.0 id: "upmerge"
+ branch release/v1.1
+ commit id: "pin version to 1.1" tag: "v1.1.0"
+```
+
+### Cherry Picking
+
+```mermaid
+gitGraph
+ commit id: "β¦"
+ branch feature/my-contribution
+ commit id: "work"
+ commit id: "review fixes"
+ checkout main
+ merge feature/my-contribution
+ commit id: "more preview work"
+ branch release/v1.0
+ commit id: "pin version to 1.0" tag: "v1.0.0-rc.1"
+ checkout main
+ commit id: "unrelated feature"
+ checkout release/v1.0
+ branch bugfix/crash-on-startup
+ commit id: "fix the crash"
+ checkout release/v1.0
+ merge bugfix/crash-on-startup tag: "v1.0.0-rc.2"
+ checkout main
+ cherry-pick id: "fix the crash"
+ checkout release/v1.0
+ commit id: "release notes" tag: "v1.0.0"
+ checkout main
+ commit id: "next round of work"
+ branch release/v1.1
+ commit id: "pin version to 1.1" tag: "v1.1.0"
+```
+
+### Support and Retirement of old release/v* branches
+
+We use the `release/v1.0` branch after the release to be able to provide support, i.e. hotfixes to the release but otherwise it stays stagnant. This branch keeps living on until we decided to cut the next release `v1.1` and successfully published it through its branch `release/v1.1`. **THEN** we can delete the old release branch `release/v1.0` and cease support for this release.
+Since we're an open source project and work with git tags, people on an older release can always go back in time, branch off an old version and apply their own hotfixes. We are happy to accept those as a PR, re-open the old release branch and publish another hotfix release **if** and **when** we see the need.
+
+> [!WARNING]
+> The release branch `release/v1.0` stays alive but stagnant, `main` moves forward. Once we cut release `v1.1` we introduce branch `release/v1.1` and the previous release branch `release/v1.0` can retire
+
+Once we feel comfortable with our release, we can `git tag` our release with the appropiate version, which triggers [`publish-packages-release.yml`](../.github/workflows/publish-packages-release.yml) to run the publish release pipeline.
+
+## References
-Once we feel comfortable with our release, we can `git tag` our release with the appropiate version, which triggers our CI to run the publish release pipeline.
+- [CONTRIBUTING.md](../CONTRIBUTING.md)
+- [docs/agents/release-and-versioning.md](agents/release-and-versioning.md)
-- TODO: put in the correct references here
- TODO: Mermaid diagram showing the branches and maybe a few examples of how to merge
- TODO: cli commands examples for release candidate and actual release
From 738390f7b7bfa0c8b8133ec53168315cbeb4bd7e Mon Sep 17 00:00:00 2001
From: Chrison Simtian
Date: Sun, 16 Aug 2026 12:53:59 +1200
Subject: [PATCH 04/10] Fix nbgv release.branchName to include the v prefix
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
release.branchName was release/{version}, which generates release/10.5 β
missing the v that every existing production branch (release/v10.4,
support/v10) and the validate-ref job's regex both expect.
Co-Authored-By: Claude Opus 5 (1M context)
---
version.json | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/version.json b/version.json
index 79a7ae20..a54b3c28 100644
--- a/version.json
+++ b/version.json
@@ -20,6 +20,6 @@
"release": {
"versionIncrement": "minor",
"firstUnstableTag": "preview",
- "branchName": "release/{version}"
+ "branchName": "release/v{version}"
}
}
From ec7cea33b8f0bbb2ae04f4b2c6bd52a9127edd1e Mon Sep 17 00:00:00 2001
From: Chrison Simtian
Date: Sun, 16 Aug 2026 12:54:10 +1200
Subject: [PATCH 05/10] Add ADR-0012: current state is semver 10.x, North Star
is CalVer + GitFlow
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
ADR-0004 decided calendar versioning in 2026 and it was never implemented.
No release/YYYY branch was ever cut, no YYYY.x tag ever pushed, and
version.json never carried a CalVer core β while AGENTS.md, the runbook,
the agent reference, and both workflow headers all described that world as
current. The runbook also claimed GitLab Flow, which was never the intent.
The versioning practice itself is healthy and self-correcting: the preview
core is rolled forward as part of each cut, and the rc counter was moved off
git height once it jumped rc.3 to rc.23. None of that was written down.
Records the split the documentation now follows β Current state is kept true
and falsifiable against the repo; North Star (CalVer + full GitFlow,
including a long-lived develop) is explicitly unimplemented. Notes that
GitFlow's develop reopens what ADR-0008 closed and will need to supersede it.
Co-Authored-By: Claude Opus 5 (1M context)
---
...te-semver-10x-north-star-calver-gitflow.md | 118 ++++++++++++++++++
docs/adr/README.md | 7 +-
2 files changed, 122 insertions(+), 3 deletions(-)
create mode 100644 docs/adr/0012-current-state-semver-10x-north-star-calver-gitflow.md
diff --git a/docs/adr/0012-current-state-semver-10x-north-star-calver-gitflow.md b/docs/adr/0012-current-state-semver-10x-north-star-calver-gitflow.md
new file mode 100644
index 00000000..764d107c
--- /dev/null
+++ b/docs/adr/0012-current-state-semver-10x-north-star-calver-gitflow.md
@@ -0,0 +1,118 @@
+# ADR-0012: Current state is semver `10.x`; North Star is CalVer + GitFlow
+
+## Status
+
+Accepted (2026-08-16). **Amends [ADR-0004](0004-calendar-versioning-and-dual-pace-channels.md) Β§1 (calendar versioning) by deferring it** β CalVer remains the intended destination, but is no longer described as current. Establishes **GitFlow as the branching North Star**, replacing the GitLab Flow the runbook previously claimed to follow.
+
+ADR-0004 Β§3βΒ§6 (production line, `[Experimental]` opt-in, review tiers), [ADR-0007](0007-cut-release-branch-on-demand.md) (on-demand release-branch cut), and [ADR-0008](0008-collapse-experimental-into-main.md) (`main` as sole prerelease lane) remain in force **for the current state**, re-expressed here in `release/vMAJOR.MINOR` terms. See Β§Consequences for how the North Star collides with ADR-0008.
+
+## Context
+
+ADR-0004 decided calendar versioning in 2026. Ten months on **it was never implemented**, and the gap between the written decision and the shipping reality had become actively harmful.
+
+What the repository actually looks like:
+
+- **`version.json` on `main` says `10.5.0-preview.{height}`.** It has never carried a CalVer core in anger; the one commit that flipped it (`e05d9af8`) was superseded back to the 10.x line.
+- **No `release/2026` branch was ever cut. No `2026.x` tag was ever pushed.** The only production branches in existence are `release/v10.4` and `support/v10`.
+- **The most recent GA is `v10.4.0`** (2026-08-07), cut from `release/v10.4` β a `release/vMAJOR.MINOR` branch, not the `release/YYYY` pattern ADR-0004 specified.
+- `AGENTS.md`, `docs/agents/release-and-versioning.md`, `docs/branching-and-release.md`, and the header comment in `publish-packages-preview.yml` all describe a `2026.1.0` CalVer world that does not exist.
+- The runbook opened with "We aim to follow GitLab Flow", which was never an accurate description of the intent either.
+
+The versioning *practice* is in good shape and self-corrected without the docs' help. The preview lane did strand once β `main` sat on an old core while newer releases shipped, so its previews sorted below everything on the shelf β but that was fixed in the ordinary course of cutting a release: `v10.4.0` was tagged at 10:28:56 on 2026-08-07 and `eeeca700` ("Move main's preview lane onto the 10.5 core") landed twenty seconds later. The prerelease counter went through the same correction: `-rc.1`/`-rc.3` were `{height}`-driven and jumped to `rc.23` on a 19-commit promotion, after which the field was pinned literally and has been ever since.
+
+**None of that learning is written down anywhere.** It lives in commit messages and in the maintainers' heads, while the documents that a contributor or agent would actually read describe a calendar-versioned repository that has never existed. That is the defect this ADR addresses.
+
+The root cause is not the version number. It is that **the documentation carried aspiration and current state in the same voice**, so a reader β human or agent β could not tell which statements were load-bearing. Every doc claim was equally assertive, and the false ones went unnoticed for months. The practice outran the writing, and the writing had no slot to put "where we're going" that wasn't indistinguishable from "where we are".
+
+## Decision
+
+**Separate the two, explicitly and permanently.**
+
+### 1. Current state: Semantic Versioning on the `10.x` line
+
+Ratifying what the repository already does, rather than changing it:
+
+- **`main` is the preview lane at `10.5.0-preview.{height}`** β the next unreleased minor after `10.4.0` GA, so previews sort above everything shipped.
+- **Production lines are `release/vMAJOR.MINOR`** (`release/v10.4`, next `release/v10.5`), cut on demand at the first release of the line (ADR-0007 unchanged).
+- **A release branch pins the full version literally**, prerelease segment included β `"10.4.0-rc.4"`, then `"10.4.0"` at GA. `{height}` is not used there; it made the `rc` counter track promotion size instead of release intent.
+- **`support/vMAJOR` remains the legacy maintenance pattern** (`support/v10`, pinned `"10.3"` with `versionHeightOffset: 24`).
+- **Rolling `main`'s core forward is a required step of cutting a release line**, done in the same sitting as the GA tag.
+- Breaking changes are batched to *the next major*, on no fixed calendar.
+
+One actual change: **`nbgv`'s `release.branchName` becomes `release/v{version}`.** It was `release/{version}`, which would have generated `release/10.5` β missing the `v` that every existing branch and the `validate-ref` job's pattern both expect.
+
+### 2. North Star: calendar versioning + GitFlow
+
+Recorded as direction, not as fact. Nothing below is implemented.
+
+- **CalVer `YYYY.MINOR.PATCH`** β production lines become `release/YYYY`, retired years become `support/YYYY`, breaking changes batch to the yearly cut. ADR-0004's rationale stands; only its timing changes.
+- **Full GitFlow**, including a long-lived **`develop`** as the integration trunk and preview lane, with `main` holding released code only. `release/v*` for stabilisation, `hotfix/v*` off `main`, `feature/*` off `develop`.
+- **Routine `[Experimental("FALLOUT0xx")]` gating** of all breaking surface before it lands β the discipline ADR-0008 assumed when it retired the `experimental` branch. The mechanism is current; the practice is not (one usage, `FALLOUT001` on `IPublish`).
+
+### 3. Documentation rule
+
+**Every document describing process carries a `Current state` section and a `North Star` section, and never blends them.** Current state is kept true β drift from the repository is a bug, not a stale doc. North Star is explicitly unimplemented.
+
+This applies to [docs/versioning.md](../versioning.md), [docs/branching-and-release.md](../branching-and-release.md), [AGENTS.md](../../AGENTS.md), and [docs/agents/release-and-versioning.md](../agents/release-and-versioning.md).
+
+### Channel summary β current state (revising ADR-0008's table)
+
+| Channel | Built from | Cadence | Version shape | Publishes to | Review tier |
+|---|---|---|---|---|---|
+| **preview** | `main` | per-commit | `10.5.0-preview..g` | GitHub Packages (test) | ordinary |
+| **rc** | `release/v10.5` pre-GA | per cut | `10.5.0-rc.2` | nuget.org (opt-in) + GH Packages | rigorous |
+| **stable** | `release/v10.5` tags | non-breaking minor/patch | `10.5.3` | nuget.org (opt-in) + GH Packages + GH Releases | rigorous |
+| **legacy** | `support/v10` | security/critical only | `10.3.x` | nuget.org (opt-in) + GH Packages | rigorous |
+| **`[Experimental]` APIs** | any channel | per-feature | rides the package | (the package) | opt-in by consumer |
+
+## Consequences
+
+### Positive
+
+- **The documentation describes the repository again.** One version scheme, matching `version.json`, the branches that exist, and the tags actually pushed.
+- **Hard-won operational knowledge is finally written down.** The preview-core ordering rule and the `-rc.N` pinning rule were both learned by shipping the bug and fixing it, and both lived only in commit messages. [docs/versioning.md](../versioning.md) now carries them as named traps with the evidence attached.
+- **Continuity with everything shipped.** `10.1.x` β `10.3.x` β `10.4.0` β `10.5.0` reads as one history; a CalVer jump to `2026.1.0` would have been a discontinuity in every consumer's upgrade path.
+- **Aspiration is preserved rather than deleted.** ADR-0004's reasoning was never refuted β it was only ever unimplemented. The North Star section keeps it visible and actionable instead of quietly dropping it.
+- **The failure mode is now structural rather than a matter of vigilance.** A claim in a Current state section is falsifiable against the repo; the drift that caused this ADR would have been caught by reading one table.
+
+### Negative
+
+- **The North Star collides with [ADR-0008](0008-collapse-experimental-into-main.md), which will need superseding when we adopt GitFlow.** ADR-0008 decided `main` is the sole prerelease lane and steady state is `main` + `support/*`. Full GitFlow reintroduces a long-lived `develop` carrying exactly the preview lane ADR-0008 consolidated onto `main` β and reintroduces the forward-port obligation ADR-0008 removed after `experimental` drifted ~17 commits behind. **This is the same failure mode that killed `experimental`**, and adopting GitFlow means answering for it explicitly, not by omission.
+- **CalVer and GitFlow have to land together or not at all.** Under GitFlow the `{height}` core and `publicReleaseRefSpec`'s exclusion both move from `main` to `develop`. Sequencing them separately means two migrations of the same fields.
+- **Cadence is not legible from the version number** until CalVer lands, which was one of ADR-0004's stated goals. Accepted in the interim: the `-preview`/`-rc`/GA ladder and release notes carry that signal.
+- **ADR-0004 is now qualified by three ADRs** β Β§2 by ADR-0008, Β§1 by this one β leaving only Β§3βΒ§6 in force. It is past the point where a consolidated replacement would be clearer than the amendment chain.
+
+### Neutral
+
+- ADR-0007 (on-demand cut) is unchanged in substance; only the branch-name pattern it references changes from `release/YYYY` to `release/vMAJOR.MINOR`, and reverts under the North Star.
+- `publicReleaseRefSpec` keeps its `\d{4}` CalVer patterns. They match nothing today and cost nothing; a future CalVer adoption needs no change to that field.
+- **The burned `11.0.x` range is a footnote, not an open decision.** `11.0.1`β`11.0.18` were published then unlisted, and NuGet never frees a version that has existed. Under the CalVer North Star the next major is the yearly cut (e.g. `2027.0.0`), which never touches the `11.x` space.
+- Labels `target/vCurrent` / `target/vNext` are already evergreen and need no change. `target/2026` exists but is unused.
+
+## Alternatives considered
+
+### A. Adopt calendar versioning now β `main` β `2026.1.0-preview.{height}`
+
+Finally implement ADR-0004: move `main` to a `2026.1.0` core, cut `release/2026` on demand, leave `10.x` to `support/v10`.
+
+**Rejected for now, retained as North Star.** It would have made ADR-0004 true and neatly sidestepped the burned `11.0.x` space. But it buys those with a version discontinuity for every consumer, a second branch-naming migration in one year, and β critically β it pairs with a GitFlow restructure that is a far larger change than a version-core bump. Bundling a live migration into a documentation-correction PR is how the original drift happened: ADR-0004 was written and the implementation never followed. Correct the record first; adopt the North Star as its own deliberate piece of work.
+
+### B. Change `version.json` as part of this ADR
+
+An earlier draft of this ADR treated the preview core as an open defect and proposed moving it. That was based on a **stale fork checkout** reading `10.0.0-preview.{height}`; `upstream/main` had already moved to `10.5.0-preview.{height}` at the 10.4.0 GA.
+
+**Withdrawn β there was nothing to fix.** Recorded because the mistake is instructive: the version core was correct and the *documentation* was wrong, which is the exact inversion this ADR exists to prevent. A reader who trusted the docs over the repo would have "fixed" a working lane. (The `release.branchName` typo in Β§1 is a genuine, separate change.)
+
+### C. Delete the CalVer aspiration entirely
+
+Retire ADR-0004 Β§1 outright and commit to semver indefinitely.
+
+**Rejected.** Nothing about ADR-0004's reasoning was shown to be wrong; it was shown to be unscheduled. Deleting it would discard a considered decision because of an implementation gap, and the burned `11.0.x` range makes a future semver major genuinely awkward in a way CalVer avoids. The North Star section exists precisely so aspiration can be kept without being mistaken for fact.
+
+## References
+
+- [ADR-0004: Calendar versioning + dual-pace channels](0004-calendar-versioning-and-dual-pace-channels.md) β Β§1 deferred here, retained as North Star; Β§2 previously superseded by ADR-0008; Β§3βΒ§6 in force.
+- [ADR-0008: Collapse `experimental` into `main`](0008-collapse-experimental-into-main.md) β in force for current state; **will need superseding** if the GitFlow North Star is adopted.
+- [ADR-0007: Cut the release branch on demand](0007-cut-release-branch-on-demand.md) β reaffirmed, re-expressed for `release/vMAJOR.MINOR`.
+- [docs/versioning.md](../versioning.md) β living document: nbgv mechanics, height, the two production traps.
+- [docs/branching-and-release.md](../branching-and-release.md) β maintainer runbook.
diff --git a/docs/adr/README.md b/docs/adr/README.md
index 811242de..b0ab6276 100644
--- a/docs/adr/README.md
+++ b/docs/adr/README.md
@@ -37,7 +37,8 @@ If you change a decision, do NOT silently rewrite the old ADR β add a new one
| [0002](0002-cross-provider-auth-and-secret-conventions.md) | Cross-provider auth and secret conventions | Proposed |
| [0002](0002-v11-off-nuget-by-default.md) | v11 publishes to GitHub Packages by default; nuget.org opt-in | Accepted |
| [0003](0003-variables-and-substitution.md) | Variables and `${β¦}` substitution layer | Proposed |
-| [0004](0004-calendar-versioning-and-dual-pace-channels.md) | Calendar versioning + dual-pace channels (edge/stable) + experimental APIs | Accepted (Β§3 amended by 0007; channel ladder Β§2 superseded by 0008) |
-| [0007](0007-cut-release-branch-on-demand.md) | Cut `release/YYYY` on demand, not preemptively | Accepted |
-| [0008](0008-collapse-experimental-into-main.md) | Collapse `experimental` into `main`; `main` is the sole prerelease lane | Accepted |
+| [0004](0004-calendar-versioning-and-dual-pace-channels.md) | Calendar versioning + dual-pace channels (edge/stable) + experimental APIs | Accepted (Β§3 amended by 0007; channel ladder Β§2 superseded by 0008; CalVer Β§1 deferred by 0012 β retained as North Star) |
+| [0007](0007-cut-release-branch-on-demand.md) | Cut the release branch on demand, not preemptively | Accepted (branch pattern re-expressed by 0012) |
+| [0008](0008-collapse-experimental-into-main.md) | Collapse `experimental` into `main`; `main` is the sole prerelease lane | Accepted (in force for current state; to be revisited if 0012's GitFlow North Star is adopted) |
| [0010](0010-no-telemetry-collection.md) | Fallout collects no telemetry | Accepted |
+| [0012](0012-current-state-semver-10x-north-star-calver-gitflow.md) | Current state is semver `10.x`; North Star is CalVer + GitFlow | Accepted |
From 5f0ee47aec845d1290376287560711f33d00b148 Mon Sep 17 00:00:00 2001
From: Chrison Simtian
Date: Sun, 16 Aug 2026 12:54:22 +1200
Subject: [PATCH 06/10] Add docs/versioning.md covering nbgv mechanics and the
two release traps
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
Captures knowledge that lived only in commit messages: git height is
automatic and preview-only, a release branch pins the full version
literally (rc counter included) because height tracked promotion size
rather than release intent, and support/v10 carries the one hand-set
versionHeightOffset.
Documents both traps that have shipped bugs β main's preview core must roll
forward in the same sitting as a cut or previews strand below the release,
and tag builds run detached so they match no publicReleaseRefSpec entry,
which is why the release workflow sets PublicRelease: true. Adds the
end-to-end cut commands and the branch naming patterns.
Co-Authored-By: Claude Opus 5 (1M context)
---
docs/versioning.md | 179 +++++++++++++++++++++++++++++++++++++++++++++
1 file changed, 179 insertions(+)
create mode 100644 docs/versioning.md
diff --git a/docs/versioning.md b/docs/versioning.md
new file mode 100644
index 00000000..2363ce67
--- /dev/null
+++ b/docs/versioning.md
@@ -0,0 +1,179 @@
+# Versioning
+
+Where our version numbers come from, why some are automatic and some are not, and the two traps that have bitten us in production.
+
+> [!NOTE]
+> This document has two halves. **Current state** describes what the repository does *today* β it is kept true, and if it drifts from reality that's a bug. **North Star** describes where we intend to go next; nothing in it is implemented.
+
+---
+
+# Current state
+
+We use [Nerdbank.GitVersioning](https://github.com/dotnet/Nerdbank.GitVersioning) (nbgv). It's wired in centrally β `Directory.Build.props` adds the `PackageReference`, `Directory.Packages.props` pins the version β so no project opts in individually. The single source of truth for the number is [`version.json`](../version.json), and it's **per-branch**: each long-lived branch carries its own copy with its own `version` field.
+
+We ship **Semantic Versioning on the `10.x` line.**
+
+## The one-paragraph version
+
+`main` produces previews with an automatic, always-moving number. Release branches produce releases with a number we pin by hand. That's the whole model β the rest of this half is why.
+
+## What each branch produces
+
+| Branch | `version.json` `version` | Produces | Published to |
+|---|---|---|---|
+| `main` | `10.5.0-preview.{height}` | `10.5.0-preview..g` | GitHub Packages only |
+| `release/v10.4` (last GA) | `10.4.0` | `10.4.0` | GitHub Packages + Releases; nuget.org opt-in |
+| `support/v10` (legacy) | `10.3` + `versionHeightOffset: 24` | `10.3.x` | security/critical only |
+
+A release branch pins the **full** version, prerelease segment included β `10.4.0-rc.4`, then `10.4.0` at GA β not just the `MAJOR.MINOR` core.
+
+## Height is automatic β and only shows up on previews
+
+`{height}` is **git height**: the number of commits since the `version` field in `version.json` last changed. nbgv computes it. Nobody sets it by hand.
+
+That placeholder only appears on `main`, deliberately β a per-commit preview lane needs a number that moves on its own, without a human bumping anything.
+
+Release branches don't use it. `release/v10.4` pins `"version": "10.4.0"`, so the number comes straight out of the file and nbgv appends nothing.
+
+> [!IMPORTANT]
+> **Changing the `version` field resets the height to zero.** The next preview after such a change restarts at `.1`, not wherever the count had reached. Fine as long as the new core sorts *above* the old one, but it's a visible discontinuity β say so in the commit message.
+
+There is exactly one place we nudge height by hand: `support/v10` carries `"versionHeightOffset": 24`, keeping that line's numbering continuous across an earlier restructure.
+
+## `-rc.N` is pinned by hand, on purpose
+
+On a release branch the prerelease number is written literally β `10.5.0-rc.1`, then `10.5.0-rc.2` β and you bump it yourself for each candidate.
+
+We tried letting `{height}` drive it, and the v10.4 cycle is the record of why it doesn't work. `v10.4.0-rc.1` and `-rc.3` were cut with `"version": "10.4.0-rc.{height}"`; the number then tracks *however many commits a promotion happened to carry* rather than release intent, and on the first 19-commit promotion it sent `rc.3` straight to `rc.23`. From `-rc.4` onward the field was pinned literally (`"10.4.0-rc.4"`, `"10.4.0-rc.5"`, then `"10.4.0"`), and that's the practice now.
+
+Two consequences:
+
+- Every commit on a release branch reports the same version until you bump. Intended.
+- **Tagging twice without bumping republishes an existing version.** `dotnet nuget push --skip-duplicate` swallows that silently, so packages simply don't update and nothing fails. Check the number before you tag.
+
+## Trap 1 β the preview core must stay ahead of the last GA
+
+`main` holds newer code than any release branch, so its previews must sort *above* everything shipped. They're compared as ordinary SemVer, and a prerelease sorts **below** the release of the same core: `10.4.0-preview.9` < `10.4.0`.
+
+So once `10.4.0` is GA, `main` must move to a core that hasn't shipped β `10.5.0-preview.{height}`. If it stays on the shipped core, or an older one, anyone who has seen the GA will never be offered a preview again.
+
+We have shipped this bug. `main` sat at `10.0.0-preview.{height}` β a core that predated the whole 10.4 line β so its previews sorted below every release on the shelf.
+
+It's fixed, and the fix is the practice to copy: `10.4.0` was tagged at 10:28:56 on 2026-08-07, and `eeeca700` ("Move main's preview lane onto the 10.5 core") landed twenty seconds later. Rolling the core forward was part of the cut, not a follow-up.
+
+> [!WARNING]
+> **Rolling `main`'s core forward is part of cutting a release line, not an afterthought.** The moment `release/v10.N` is cut, `main` moves to `10.(N+1).0-preview.{height}`. Do it in the same sitting β the twenty-second gap above is the standard to hold.
+
+## Trap 2 β tag builds are detached, and nbgv notices
+
+`publicReleaseRefSpec` decides whether nbgv considers a build "public". Public builds get a clean number; non-public builds get a `.g` suffix appended so they can't be mistaken for a release.
+
+Every entry in that list is a **branch** ref, and nbgv never matches the spec against `refs/tags/*`. The release workflow checks out the tag β a detached HEAD β which therefore matches nothing, and nbgv treats a production release build as non-public.
+
+That is how `v10.4.0-rc.3` shipped to consumers as `10.4.0-rc.3.geabd043cc2`.
+
+The fix is already in [`publish-packages-release.yml`](../.github/workflows/publish-packages-release.yml): the Test+Pack step sets `PublicRelease: true` explicitly. **Don't remove it**, and if you add another job that packs from a tag, set it there too.
+
+`main` is deliberately *absent* from `publicReleaseRefSpec`. That's what gives previews their `.g` suffix, and it's intentional β previews should never look like releases.
+
+## Branch naming patterns
+
+nbgv's `release.branchName` is `release/v{version}`, so the tooling and the conventions below agree. (It was `release/{version}` until this document landed β which would have generated `release/10.5`, missing the `v`.)
+
+| Pattern | Example | Meaning |
+|---|---|---|
+| `release/v.` | `release/v10.5` | Production line. Cut on demand at first release, not preemptively. |
+| `support/v` | `support/v10` | Legacy maintenance line. Security and critical fixes only. |
+| `hotfix/v.` | `hotfix/v10.4` | Short-lived fix branch off a support line. |
+| `feature/`, `bugfix/`, `chore/`, `docs/` | `feature/plugin-host` | Short-lived, target `main`, rebase-merged. |
+
+Tags carry a `v` prefix (`v10.5.0`) so the `v*` tag-protection ruleset applies. The package version core is the bare number (`10.5.0`).
+
+## Cutting the next line
+
+```bash
+# 1. Cut the line from main.
+git switch main && git pull upstream main
+git switch -c release/v10.5
+
+# 2. Pin the first candidate literally β no {height}, no bare core.
+# version.json -> "version": "10.5.0-rc.1"
+# release/v10.5 matches ^refs/heads/release/v\d+\.\d+$ in publicReleaseRefSpec,
+# so it is a public ref and gets clean numbers.
+git commit -am "Pin release/v10.5 to 10.5.0-rc.1"
+git push -u upstream release/v10.5
+
+# 3. Roll main forward IN THE SAME SITTING. See Trap 1.
+git switch main
+# version.json -> "version": "10.6.0-preview.{height}"
+git commit -am "Move main's preview lane onto the 10.6 core"
+git push upstream main
+
+# 4. Verify, then tag the candidate.
+git switch release/v10.5
+dotnet nbgv get-version # expect clean 10.5.0-rc.1 β no -g suffix
+git tag v10.5.0-rc.1
+git push upstream v10.5.0-rc.1
+
+# 5. Each further candidate: bump the pinned field BY HAND, then tag.
+# version.json -> "version": "10.5.0-rc.2"
+git commit -am "Bump release/v10.5 to 10.5.0-rc.2"
+git tag v10.5.0-rc.2 && git push upstream v10.5.0-rc.2
+
+# 6. GA: drop the prerelease segment, then tag.
+# version.json -> "version": "10.5.0"
+git commit -am "Pin release/v10.5 to 10.5.0 for GA"
+git tag v10.5.0 && git push upstream v10.5.0
+
+# 7. nuget.org is opt-in β tags alone never publish there.
+gh workflow run publish-packages-release.yml \
+ --repo Fallout-build/Fallout \
+ -f tag=v10.5.0 -f publish-to-nugetorg=true
+```
+
+> [!CAUTION]
+> Step 5 is the one people skip. Tagging twice without bumping the pinned field republishes an existing version, and `--skip-duplicate` swallows the failure silently β the release "succeeds" and ships nothing.
+
+## Unstable public surface
+
+`[Experimental("FALLOUT0xx")]` marks opt-in public API that carries no stability guarantee β adding or removing it is not a breaking change. The mechanism is wired up and the diagnostic-ID registry exists ([docs/experimental-apis.md](experimental-apis.md)); today it's used in one place, `FALLOUT001` on `IPublish`.
+
+---
+
+# North Star
+
+Not implemented. This is the direction, recorded so the gap stays visible rather than being rediscovered each time.
+
+## Calendar versioning
+
+Move from semver `10.x` to **`YYYY.MINOR.PATCH`** β mechanically valid SemVer 2.0 (all three components numeric), so nbgv, NuGet, and version ordering keep working unchanged. The major *is* the calendar year.
+
+| | Current | North Star |
+|---|---|---|
+| Preview lane | `10.5.0-preview.{height}` | `YYYY.MINOR.0-preview.{height}` |
+| Production line | `release/v10.5` pinning `"10.5.0-rc.N"` β `"10.5.0"` | `release/YYYY` pinning `"YYYY.MINOR.P-rc.N"` β `"YYYY.MINOR.P"` |
+| Legacy line | `support/v10` | `support/v10` unchanged; retired years become `support/YYYY` |
+| Next major | `10.6`, `10.7`, β¦ | the yearly cut |
+| Breaking changes | batched to the next major, no fixed date | batched to the **yearly** major cut |
+
+`publicReleaseRefSpec` already carries the `\d{4}` patterns (`^refs/heads/release/\d{4}$`, `^refs/heads/support/\d{4}$`). They match nothing today and cost nothing β adopting CalVer needs no change to that field.
+
+The full rationale is [ADR-0004](adr/0004-calendar-versioning-and-dual-pace-channels.md); [ADR-0012](adr/0012-current-state-semver-10x-north-star-calver-gitflow.md) records why it is deferred rather than abandoned.
+
+> [!NOTE]
+> **The burned `11.0.x` range is a footnote, not a blocker.** `11.0.1`β`11.0.18` were published then unlisted, and NuGet never frees a version that has existed. Under CalVer the next major is the yearly cut (e.g. `2027.0.0`), which never touches the `11.x` space β so the range is simply never revisited.
+
+## Routine `[Experimental]` gating
+
+Today the attribute exists and is used once. The intent is that **every breaking surface is gated behind it before landing**, so breaking work can accumulate on the integration trunk without a separate branch β the discipline ADR-0008 assumed when it retired the `experimental` lane.
+
+## Where the versioning North Star meets the branching one
+
+CalVer pairs with the GitFlow North Star in [branching-and-release.md](branching-and-release.md). Under full GitFlow the preview lane publishes from `develop` rather than `main`, which means `version.json`'s `{height}` core and `publicReleaseRefSpec`'s exclusion both move to `develop`. That is a direct conflict with [ADR-0008](adr/0008-collapse-experimental-into-main.md) ("`main` is the sole prerelease lane") and will need a superseding ADR when we adopt it β the two North Stars have to land together or not at all.
+
+## See also
+
+- [branching-and-release.md](branching-and-release.md) β how branches flow and how to publish a release
+- [ADR-0012](adr/0012-current-state-semver-10x-north-star-calver-gitflow.md) β why `10.x` now, CalVer + GitFlow next
+- [ADR-0004](adr/0004-calendar-versioning-and-dual-pace-channels.md) β the deferred calendar-versioning decision
+- [docs/experimental-apis.md](experimental-apis.md) β the `FALLOUT0xx` diagnostic-ID registry
From 93a47e4348bfea990e22dc3f923b73a5c398f271 Mon Sep 17 00:00:00 2001
From: Chrison Simtian
Date: Sun, 16 Aug 2026 12:54:22 +1200
Subject: [PATCH 07/10] Split the branching runbook into Current state and
North Star
Replaces the GitLab Flow opener with the trunk-based model we actually run,
and moves GitFlow into a North Star section with a develop-based diagram.
Keeps the existing pipeline, upmerge and cherry-pick diagrams as-is.
Closes the three TODOs: branch naming patterns and the release CLI sequence
now live in versioning.md, and the branch diagram the last TODO asked for is
the GitFlow one. Flags that adopting develop reopens the drift problem
ADR-0008 removed the experimental branch to solve.
Co-Authored-By: Claude Opus 5 (1M context)
---
docs/branching-and-release.md | 148 +++++++++++++++++++++++++++++-----
1 file changed, 130 insertions(+), 18 deletions(-)
diff --git a/docs/branching-and-release.md b/docs/branching-and-release.md
index 8b30cf22..a88d60c4 100644
--- a/docs/branching-and-release.md
+++ b/docs/branching-and-release.md
@@ -1,29 +1,49 @@
# Branching and release flow
-We aim to follow [Gitlab Flow](https://about.gitlab.com/topics/version-control/what-is-gitlab-flow/), a lightweight Gitflow alternative.
-What does that mean for you as a contributor or maintainer of this project?
+What does branching mean for you as a contributor or maintainer of this project?
+
+> [!NOTE]
+> This document has two halves. **Current state** describes what we do *today* β it is kept true, and if it drifts from the repository that's a bug. **North Star** describes where we intend to go next; nothing in it is implemented yet.
+
+---
+
+# Current state
+
+We run a **trunk-based** model: `main` is both the integration trunk and the preview lane, with production lines cut off it on demand.
+
+## Lines live right now
+
+Keep this block current β the examples further down use these values.
+
+| Line | Branch | Ships | Latest |
+|---|---|---|---|
+| Preview | `main` | `10.5.0-preview..g` β GitHub Packages, per commit | rolling |
+| Production | `release/v10.4` | `10.4.0` β GitHub Packages + GH Release; nuget.org opt-in | `v10.4.0` |
+| Legacy | `support/v10` | `10.3.x` security/critical only | `10.3.47` |
+
+`main` is deliberately **not** in `publicReleaseRefSpec`, which is why its previews carry the `.g` suffix β they're non-public builds by design. Production lines are listed there, so their packages are clean.
+
+A Docker-based local NuGet server is available for pre-merge testing via `tests/integration/docker-compose.yml`.
## How to contribute code
1. You develop on a local fork
2. You raise a PR once your work is ready for review
-3. Target `main` on the Fallout-Upstream
+3. Target `main` on the Fallout upstream
4. Your code gets merged
-Every push to `main` triggers a pre-release on Github, including publishing nuget packages to Github (but not Nuget.org!).
-This is a cheap way to get our hands on pre-release packages without the cost of publishing anything to official Package Repositories.
+Every push to `main` triggers a pre-release on GitHub, including publishing NuGet packages to GitHub Packages (but **not** nuget.org). This is a cheap way to get our hands on pre-release packages without the cost of publishing anything to official package repositories.
+
+Merges are **rebase-only**. Plain merge commits are disabled by repo setting; **squash is still enabled at the repo level**, so on release branches the convention β not the setting β is what keeps squashes out. Squashing a promotion would collapse it into one opaque commit, defeating the point of promoting reviewed commits verbatim. Curate your commits before final approval.
## How to publish a new release
-Sometimes it becomes necessary to create a stabilisation branch to make sure we iron out the worst bugs before pushing a release.
-For this purpose Gitlab Flow allows us to create branches, i.e. `release/v1.0`
-> [!NOTE]
-> While a release branch exists, it becomes necessary to raise some PRs against `release/v1.0` and **then** upmerge those changes against `main` as well
+Sometimes it becomes necessary to create a stabilisation branch to make sure we iron out the worst bugs before pushing a release. For this purpose we create a release branch, i.e. `release/v1.0`.
-> [!INFO]
-> Please make sure to follow our naming pattern for release branches
+> [!NOTE]
+> While a release branch exists, it becomes necessary to raise some PRs against `release/v1.0` and **then** upmerge those changes against `main` as well.
-TODO: Create a documnent describing our naming patterns etc. in detail. Can probably sit with AI instructions.
+Branch and tag naming patterns are in [versioning.md](versioning.md#branch-naming-patterns). The short version: production lines are `release/v.`, legacy maintenance is `support/v`, and tags carry a `v` prefix so the `v*` protection ruleset applies.
```mermaid
flowchart TD
@@ -106,18 +126,110 @@ gitGraph
### Support and Retirement of old release/v* branches
-We use the `release/v1.0` branch after the release to be able to provide support, i.e. hotfixes to the release but otherwise it stays stagnant. This branch keeps living on until we decided to cut the next release `v1.1` and successfully published it through its branch `release/v1.1`. **THEN** we can delete the old release branch `release/v1.0` and cease support for this release.
+We use the `release/v1.0` branch after the release to be able to provide support, i.e. hotfixes to the release, but otherwise it stays stagnant. This branch keeps living on until we decide to cut the next release `v1.1` and successfully publish it through its branch `release/v1.1`. **THEN** we can delete the old release branch `release/v1.0` and cease support for this release.
+
Since we're an open source project and work with git tags, people on an older release can always go back in time, branch off an old version and apply their own hotfixes. We are happy to accept those as a PR, re-open the old release branch and publish another hotfix release **if** and **when** we see the need.
> [!WARNING]
-> The release branch `release/v1.0` stays alive but stagnant, `main` moves forward. Once we cut release `v1.1` we introduce branch `release/v1.1` and the previous release branch `release/v1.0` can retire
+> The release branch `release/v1.0` stays alive but stagnant, `main` moves forward. Once we cut release `v1.1` we introduce branch `release/v1.1` and the previous release branch `release/v1.0` can retire.
+
+One line is exempt: **`support/v10` is a long-lived legacy maintenance branch**, not a stagnant release branch. It takes security and critical fixes only, and it does not retire when a newer line is cut.
+
+Once we feel comfortable with our release, we can `git tag` our release with the appropriate version, which triggers [`publish-packages-release.yml`](../.github/workflows/publish-packages-release.yml) to run the publish release pipeline.
+
+### Commands, end to end
+
+The exact sequence β cutting the line, pinning the candidate, rolling `main` forward, tagging, and opting into nuget.org β is in [versioning.md β Cutting the next line](versioning.md#cutting-the-next-line). It lives there because every step is a version-number decision, and splitting it across two documents is how the two drift apart.
+
+Two things worth knowing before you start:
+
+- **Roll `main`'s preview core forward in the same sitting as the cut.** Skip it and the preview lane strands below the release you just shipped.
+- **nuget.org never publishes from a tag push.** It needs `workflow_dispatch` with `publish-to-nugetorg=true`, and then still clears an environment approval.
+
+### If a publish fails partway through
+
+Every `dotnet nuget push` uses `--skip-duplicate`, so re-running a publish job is idempotent on packages that already made it. For a transient failure mid-publish, re-run against the existing tag:
+
+```bash
+# Routine re-run β leave publish-to-nugetorg false
+gh workflow run publish-packages-release.yml --ref release/v10.4 -f tag=v10.4.0
+
+# Stabilised re-run β include the flag to retry the nuget.org push
+gh workflow run publish-packages-release.yml --ref release/v10.4 -f tag=v10.4.0 -f publish-to-nugetorg=true
+```
+
+## What's protected
+
+`main`, every release line, and every `support/*` branch share the same profile: required `ubuntu-latest` status check, linear history, CODEOWNER review (0 additional approvals), no direct pushes, no force-push or deletion, conversation resolution required, admins able to bypass in emergencies. Stale approvals are **not** dismissed when new commits land (`dismiss_stale_reviews: false`).
-Once we feel comfortable with our release, we can `git tag` our release with the appropiate version, which triggers [`publish-packages-release.yml`](../.github/workflows/publish-packages-release.yml) to run the publish release pipeline.
+How it's applied differs by branch:
+
+- **Release lines** β covered by the pattern-based ruleset on `refs/heads/release/**` ([19766406](https://github.com/Fallout-build/Fallout/rules/19766406)), so protection attaches automatically at branch creation. Payload committed at `.github/release-branch-ruleset.json`. **Nothing to apply by hand.**
+- **`main` and `support/*`** β classic per-branch protection, configured individually.
+
+`v*` tags are a separate ruleset ([17017817](https://github.com/Fallout-build/Fallout/rules/17017817)) covering creation, deletion and update, bypassable by repo admins only.
+
+For the promotion and hotfix flows (promoting `main β release/v*`, forward-porting a stable-urgent fix, and deprecating a `support/*` line), see the upmerge and cherry-pick diagrams above.
+
+---
+
+# North Star
+
+Not implemented. Recorded so the gap stays visible rather than being rediscovered each time.
+
+## GitFlow
+
+We aim to move to **[GitFlow](https://nvie.com/posts/a-successful-git-branching-model/)** proper, including a long-lived `develop` branch.
+
+```mermaid
+gitGraph
+ commit id: "β¦"
+ branch develop
+ commit id: "integration"
+ branch feature/my-contribution
+ commit id: "work"
+ checkout develop
+ merge feature/my-contribution
+ commit id: "more work"
+ branch release/v1.1
+ commit id: "pin version to 1.1" tag: "v1.1.0-rc.1"
+ commit id: "stabilise" tag: "v1.1.0-rc.2"
+ checkout main
+ merge release/v1.1 tag: "v1.1.0"
+ checkout develop
+ merge release/v1.1 id: "back-merge"
+ checkout main
+ branch hotfix/v1.1.1
+ commit id: "urgent fix"
+ checkout main
+ merge hotfix/v1.1.1 tag: "v1.1.1"
+ checkout develop
+ merge hotfix/v1.1.1 id: "back-merge fix"
+```
+
+| | Current | North Star |
+|---|---|---|
+| Integration trunk | `main` | `develop` |
+| Preview lane publishes from | `main` | `develop` |
+| `main` holds | trunk + previews | **released code only**, tagged at each GA |
+| Stabilisation | `release/v*` cut from `main` | `release/v*` cut from `develop` |
+| Urgent fixes | `hotfix/*` off a support line | `hotfix/*` off `main`, back-merged to `develop` |
+| Feature branches | off `main` | off `develop` |
+
+## Calendar versioning
+
+Production lines become `release/YYYY`, retired years become `support/YYYY`, and breaking changes batch to the yearly cut. See [versioning.md β North Star](versioning.md#north-star) and [ADR-0004](adr/0004-calendar-versioning-and-dual-pace-channels.md).
+
+## The two have to land together
+
+Under GitFlow the preview lane moves to `develop`, which means `version.json`'s `{height}` core and `publicReleaseRefSpec`'s exclusion both move with it. Sequencing CalVer and GitFlow separately means migrating the same fields twice.
+
+> [!CAUTION]
+> **Adopting `develop` reopens a question we already answered once.** [ADR-0008](adr/0008-collapse-experimental-into-main.md) removed the `experimental` branch precisely because a second long-lived lane drifted ~17 commits *behind* `main` and carried no unique work β the forward-port discipline never happened. A `develop` branch takes on that same obligation in the opposite direction. Adopting GitFlow means saying explicitly what will make the back-merge stick this time, and superseding ADR-0008 rather than quietly contradicting it.
## References
+- [versioning.md](versioning.md) β where version numbers come from, and the two traps that have bitten us
- [CONTRIBUTING.md](../CONTRIBUTING.md)
- [docs/agents/release-and-versioning.md](agents/release-and-versioning.md)
-
-- TODO: Mermaid diagram showing the branches and maybe a few examples of how to merge
-- TODO: cli commands examples for release candidate and actual release
+- [ADR-0012](adr/0012-current-state-semver-10x-north-star-calver-gitflow.md) β current state vs North Star, and why
From 97668680f70830316fcb83978740ba6a8bbcc1f5 Mon Sep 17 00:00:00 2001
From: Chrison Simtian
Date: Sun, 16 Aug 2026 12:54:34 +1200
Subject: [PATCH 08/10] Correct the agent-facing docs to the shipping version
scheme
AGENTS.md and the release reference described calendar versions, release/YYYY
production lines and a yearly major cut, none of which exist. Restates them
on semver 10.x with a clearly-marked North Star, and points at versioning.md
for the mechanics.
Also fixes three claims the repository contradicts: GitVersion is no longer
referenced at all, main requires 0 approving reviews rather than CODEOWNER
review, and the v* tag ruleset is active rather than pending under milestone
13. Records that 11.0.1-11.0.18 are burned on nuget.org.
Co-Authored-By: Claude Opus 5 (1M context)
---
AGENTS.md | 22 ++++++-----
docs/agents/conventions.md | 2 +-
docs/agents/release-and-versioning.md | 54 ++++++++++++++-------------
3 files changed, 43 insertions(+), 35 deletions(-)
diff --git a/AGENTS.md b/AGENTS.md
index bf8a8923..30e172fa 100644
--- a/AGENTS.md
+++ b/AGENTS.md
@@ -12,14 +12,17 @@ Originally NUKE by [matkoch](https://github.com/matkoch); under new maintenance
**Rebrand status:** the structural rename has landed β namespaces (`Fallout.*`), package IDs, project filenames, and the global tool name (`dotnet fallout`) are all in place. Legacy `Nuke.*` lives on only as the consumer transition shims under `src/Shims/`. The mapping is a strict 1:1 prefix swap β every `Nuke.X.Y.Z` namespace and assembly is `Fallout.X.Y.Z`, with no consolidation β which `[TypeForwardedTo]` in the shims locks in place. Consumer-facing migration is [docs/migration/from-nuke.md](docs/migration/from-nuke.md).
-**Versioning & channels (calendar versioning, two-tier ladder β [ADR-0004](docs/adr/0004-calendar-versioning-and-dual-pace-channels.md), channel ladder superseded by [ADR-0008](docs/adr/0008-collapse-experimental-into-main.md)).** The project ships on **calendar versions `YYYY.MINOR.PATCH`** (mechanically valid semver; major = year). A maturity ladder feeds the production line β **GitHub Packages = test/preview; nuget.org = production**:
-- **`main` = the integration trunk + sole `-preview` channel** β default branch; both deliberate improvements/bug fixes *and* faster AI-assisted work land here. Per-commit `-preview` prereleases (`2026.1.0-preview..g`) to **GitHub Packages only β never nuget.org**. Ordinary review. (The dedicated `experimental` `-alpha` lane was removed by ADR-0008 β it ran behind `main` and carried no unique work.)
-- **`release/YYYY` = the production line** β **cut from `main` on demand at the first release of the year, not preemptively** ([ADR-0007](docs/adr/0007-cut-release-branch-on-demand.md)); until then `main` (`-preview`) is the most-stable line. Hardened deliberately, `-rc.N` β GA, non-breaking minors/patches only after the cut, rigorous review. Tags publish to nuget.org (opt-in) + GitHub Packages + GitHub Releases.
-- **Breaking changes are batched to the yearly major cut** β they accumulate on `main` gated behind `[Experimental("FALLOUT0xx")]` (or a short-lived topic branch off `main` when they can't be gated) and ship as next year's `YYYY+1.0.0`. Mid-year `main`/production is strictly non-breaking; the production-cut review is the backstop. Version ladder: `-preview` < `-rc` < GA.
-- **Legacy `support/v10`** (renamed from `release/v10`; + `hotfix/v10.x`) stays on semver `10.x`, security/critical fixes only; retired year lines become **`support/YYYY`**. **`release/v11` is retired and its branch removed** (nothing clean shipped; its work re-homed onto the 2026 line) β dead branches with no unique history are now deletable, tags are the durable release markers ([ADR-0007](docs/adr/0007-cut-release-branch-on-demand.md) Β§6).
-- Opt-in unstable public APIs are marked `[Experimental("FALLOUT0xx")]` and can ride any channel; promoting to stable = removing the attribute.
+**Versioning & channels β CURRENT STATE ([ADR-0012](docs/adr/0012-current-state-semver-10x-north-star-calver-gitflow.md)).** The project ships **semver on the `10.x` line**, trunk-based. A maturity ladder feeds the production line β **GitHub Packages = test/preview; nuget.org = production**:
+- **`main` = the integration trunk + sole `-preview` channel** β default branch; both deliberate improvements/bug fixes *and* faster AI-assisted work land here. Per-commit `-preview` prereleases (`10.5.0-preview..g`) to **GitHub Packages only β never nuget.org**. Ordinary review. (The dedicated `experimental` `-alpha` lane was removed by [ADR-0008](docs/adr/0008-collapse-experimental-into-main.md) β it ran behind `main` and carried no unique work.)
+- **`release/v.` = the production line** (currently `release/v10.4`; next `release/v10.5`) β **cut from `main` on demand at the first release of the line, not preemptively** ([ADR-0007](docs/adr/0007-cut-release-branch-on-demand.md)); until then `main` (`-preview`) is the most-stable line. Hardened deliberately, `-rc.N` β GA, non-breaking minors/patches only after the cut, rigorous review. Tags publish to nuget.org (opt-in) + GitHub Packages + GitHub Releases.
+- **A release branch pins the full version literally** β `"10.4.0-rc.4"`, then `"10.4.0"` at GA. Bump it by hand for each candidate; `{height}` is not used there. **`main`'s preview core rolls forward in the same sitting as the cut** β skipping that strands the preview lane below the shipped release. Both rules and the detached-HEAD `PublicRelease` trap are in [docs/versioning.md](docs/versioning.md).
+- **Breaking changes are batched to the next major**, on no fixed calendar β they accumulate on `main` gated behind `[Experimental("FALLOUT0xx")]` (or a short-lived topic branch off `main` when they can't be gated). Mid-year `main`/production is strictly non-breaking; the production-cut review is the backstop. Version ladder: `-preview` < `-rc` < GA.
+- **Legacy `support/v10`** (renamed from `release/v10`; + `hotfix/v10.x`) stays on `10.3.x`, security/critical fixes only, and does **not** retire when a newer line is cut. **`release/v11` is retired and its branch removed** (nothing clean shipped) β dead branches with no unique history are deletable, tags are the durable release markers ([ADR-0007](docs/adr/0007-cut-release-branch-on-demand.md) Β§6). `11.0.1`β`11.0.18` are burned on nuget.org and can never be reused.
+- Opt-in unstable public APIs are marked `[Experimental("FALLOUT0xx")]` and can ride any channel; promoting to stable = removing the attribute. The mechanism is wired up; routine use is aspirational (one usage today, `FALLOUT001`).
-**Active work** β rebrand completion + plugin-architecture internal foundation ([milestone #6](https://github.com/Fallout-build/Fallout/milestone/6)), now shipping on the `2026` line. **No public plugin SDK yet** β that's a later major ([milestone #7](https://github.com/Fallout-build/Fallout/milestone/7)). Internal middleware/listener interfaces stay `internal`; do not expose via `InternalsVisibleTo` to non-test assemblies. See [docs/roadmap.md](docs/roadmap.md) and the five open RFCs ([#97](https://github.com/Fallout-build/Fallout/issues/97)β[#101](https://github.com/Fallout-build/Fallout/issues/101)).
+**NORTH STAR β not implemented.** **Calendar versioning** (`YYYY.MINOR.PATCH`, `release/YYYY`, breaking changes batched to the yearly cut β [ADR-0004](docs/adr/0004-calendar-versioning-and-dual-pace-channels.md)) and **full GitFlow** (a long-lived `develop` as integration trunk and preview lane; `main` holds released code only). The two must land together, and GitFlow requires superseding ADR-0008. Do **not** write code, docs, or PR descriptions as though either is in force β see [docs/branching-and-release.md](docs/branching-and-release.md#north-star).
+
+**Active work** β rebrand completion + plugin-architecture internal foundation ([milestone #6](https://github.com/Fallout-build/Fallout/milestone/6)), shipping on the `10.x` line. **No public plugin SDK yet** β that's a later major ([milestone #7](https://github.com/Fallout-build/Fallout/milestone/7)). Internal middleware/listener interfaces stay `internal`; do not expose via `InternalsVisibleTo` to non-test assemblies. See [docs/roadmap.md](docs/roadmap.md) and the five open RFCs ([#97](https://github.com/Fallout-build/Fallout/issues/97)β[#101](https://github.com/Fallout-build/Fallout/issues/101)).
## Stack
@@ -49,8 +52,8 @@ To restructure an existing PR's commit history into focused commits, use the `/r
## Critical rules (read this every session)
-1. **At PR-creation time, follow the [PR-creation flow](docs/agents/release-and-versioning.md#pr-creation-flow) in `docs/agents/release-and-versioning.md`.** That flow covers working from a fork (branch off `upstream/main`, push to `origin`, PR against `upstream`), creating the PR as a draft by default, and labelling. Every PR gets a `target/vCurrent` label (or `target/vNext` for work held to next year's major) and a changelog-category label β [`.github/release.yml`](.github/release.yml) is the source of truth for the label taxonomy and AI applies the matching one whenever it raises a PR. Breaking changes additionally get a `breaking-change` label and a `β οΈ Breaking change` callout in the PR description naming the migration path β the label is what carries them into the generated release notes, and they are held for the **next yearly major** (breaking changes are batched to the year cut, not shipped mid-year). A breaking-change PR targets **`main`** with the breaking surface gated behind `[Experimental("FALLOUT0xx")]` (or, when it can't be gated, on a short-lived topic branch off `main` held for the year cut) β **never** a `release/YYYY` production train. This is non-negotiable β review will block. (Before [ADR-0008](docs/adr/0008-collapse-experimental-into-main.md) breaking work targeted the `experimental` branch; that branch is gone.)
-2. **Default to backwards compatibility.** Prefer additive over breaking changes. Before changing a public signature, removing an API, renaming a package, or altering an on-disk format, ask: can this be additive instead? `[Obsolete]` markers, transition shims (see `src/Shims/` + `Fallout.SourceGenerators.TransitionShimGenerator`), the `[Experimental("FALLOUT0xx")]` opt-in escape hatch for not-yet-stable surface, feature flags, and overload-based extension are all preferred to a hard break. When a breaking change is genuinely unavoidable, it lands on `main` (gated behind `[Experimental("FALLOUT0xx")]`, or held on a short-lived topic branch for the next yearly major), and follows rule #1's flow β the break must be deliberate, named, and migration-pathed in the PR description. See [#262](https://github.com/Fallout-build/Fallout/issues/262) for the broader discussion. The `[Experimental]` convention (diagnostic-ID scheme + registry) is documented in [docs/agents/conventions.md](docs/agents/conventions.md#experimental-for-opt-in-unstable-apis) and [docs/experimental-apis.md](docs/experimental-apis.md). Deprecations use `[Obsolete]` with a `FALLOUTOBS0xx` `DiagnosticId` so `TreatWarningsAsErrors` consumers can suppress a single deprecation β see [docs/agents/conventions.md](docs/agents/conventions.md#obsolete-for-deprecating-public-apis) and the [docs/obsolete_apis.md](docs/obsolete_apis.md) registry.
+1. **At PR-creation time, follow the [PR-creation flow](docs/agents/release-and-versioning.md#pr-creation-flow) in `docs/agents/release-and-versioning.md`.** That flow covers working from a fork (branch off `upstream/main`, push to `origin`, PR against `upstream`), creating the PR as a draft by default, and labelling. Every PR gets a `target/vCurrent` label (or `target/vNext` for work held to the next major) and a changelog-category label β [`.github/release.yml`](.github/release.yml) is the source of truth for the label taxonomy and AI applies the matching one whenever it raises a PR. Breaking changes additionally get a `breaking-change` label and a `β οΈ Breaking change` callout in the PR description naming the migration path β the label is what carries them into the generated release notes, and they are held for the **next major** (breaking changes are batched to a major cut, not shipped mid-line). A breaking-change PR targets **`main`** with the breaking surface gated behind `[Experimental("FALLOUT0xx")]` (or, when it can't be gated, on a short-lived topic branch off `main` held for the cut) β **never** a `release/v.` production train. This is non-negotiable β review will block. (Before [ADR-0008](docs/adr/0008-collapse-experimental-into-main.md) breaking work targeted the `experimental` branch; that branch is gone.)
+2. **Default to backwards compatibility.** Prefer additive over breaking changes. Before changing a public signature, removing an API, renaming a package, or altering an on-disk format, ask: can this be additive instead? `[Obsolete]` markers, transition shims (see `src/Shims/` + `Fallout.SourceGenerators.TransitionShimGenerator`), the `[Experimental("FALLOUT0xx")]` opt-in escape hatch for not-yet-stable surface, feature flags, and overload-based extension are all preferred to a hard break. When a breaking change is genuinely unavoidable, it lands on `main` (gated behind `[Experimental("FALLOUT0xx")]`, or held on a short-lived topic branch for the next major), and follows rule #1's flow β the break must be deliberate, named, and migration-pathed in the PR description. See [#262](https://github.com/Fallout-build/Fallout/issues/262) for the broader discussion. The `[Experimental]` convention (diagnostic-ID scheme + registry) is documented in [docs/agents/conventions.md](docs/agents/conventions.md#experimental-for-opt-in-unstable-apis) and [docs/experimental-apis.md](docs/experimental-apis.md). Deprecations use `[Obsolete]` with a `FALLOUTOBS0xx` `DiagnosticId` so `TreatWarningsAsErrors` consumers can suppress a single deprecation β see [docs/agents/conventions.md](docs/agents/conventions.md#obsolete-for-deprecating-public-apis) and the [docs/obsolete_apis.md](docs/obsolete_apis.md) registry.
3. **Central package versions only** β add to `Directory.Packages.props`, never `Version=` inline.
4. **Tests next to code** β every `src/Foo` has a `tests/Foo.Tests` sibling. Mirror namespaces.
5. **Stay on xUnit + FluentAssertions + Verify.** Don't introduce new test frameworks.
@@ -65,6 +68,7 @@ Full conventions + what-not-to-do list: [docs/agents/conventions.md](docs/agents
- **[docs/agents/repository-layout.md](docs/agents/repository-layout.md)** β full directory structure, project groupings, transition-shim strategy
- **[docs/agents/release-and-versioning.md](docs/agents/release-and-versioning.md)** β branching, semver policy, PR-creation flow, release pipeline, NuGet gotchas
+- **[docs/versioning.md](docs/versioning.md)** β where version numbers come from (nbgv, git height, `-rc.N` pinning) and the two traps that have shipped bugs. Read before touching `version.json` or cutting a release.
- **[docs/branching-and-release.md](docs/branching-and-release.md)** β maintainer runbook for cutting releases, hotfixing older majors, cutting new `release/vN` branches
- **[docs/adr/](docs/adr/)** β Architecture Decision Records (read `0004-calendar-versioning-and-dual-pace-channels.md` and `0001-release-branch-model.md` for the release model)
- **[docs/agents/conventions.md](docs/agents/conventions.md)** β conventions, what-not-to-do list, tool-wrapper recipe
diff --git a/docs/agents/conventions.md b/docs/agents/conventions.md
index 83ea8c73..394b482f 100644
--- a/docs/agents/conventions.md
+++ b/docs/agents/conventions.md
@@ -64,7 +64,7 @@ public sealed class NewPluginHost
- **Diagnostic-ID scheme: `FALLOUT0xx`.** Each experimental surface gets its own ID (e.g. `FALLOUT001`), allocated **sequentially and never reused** β a retired ID stays retired. Register every allocation in the [diagnostic-ID registry](../experimental-apis.md) in the same PR that introduces it.
- **Consumers must explicitly opt in.** `ExperimentalAttribute` is an *error-by-default* diagnostic: code that touches the API fails to compile until the consumer suppresses the exact ID β `#pragma warning disable FALLOUT001` around the call site, or `$(NoWarn);FALLOUT001` in their project. Opting into instability is therefore a conscious, per-API choice β which is right for a *framework* (a product devs build on), not an app.
- **Promoting to stable = removing the attribute.** Because the feature already rode the `main` test lane, deleting the `[Experimental]` line is the whole promotion β no special cross-branch dance. This is what lets stabilised work feed into the production line without a divergent fork. Adding *or* removing `[Experimental]` is **not** a breaking change.
-- **Channel discipline differs.** On the `main` (preview) test lane, churn is expected and the attribute is a courtesy. On a `release/YYYY` **production line**, any risky-but-shipped public surface **must** wear `[Experimental]` β that contract is what keeps the stable line trustworthy while still carrying new work. With the `experimental` branch retired ([ADR-0008](../adr/0008-collapse-experimental-into-main.md)), `[Experimental]` is now the primary mechanism for isolating unstable surface on `main` β including breaking changes batched toward the yearly major.
+- **Channel discipline differs.** On the `main` (preview) test lane, churn is expected and the attribute is a courtesy. On a `release/v.` **production line**, any risky-but-shipped public surface **must** wear `[Experimental]` β that contract is what keeps the stable line trustworthy while still carrying new work. With the `experimental` branch retired ([ADR-0008](../adr/0008-collapse-experimental-into-main.md)), `[Experimental]` is now the primary mechanism for isolating unstable surface on `main` β including breaking changes batched toward the next major.
- **Don't apply it speculatively.** Because the diagnostic is error-by-default, marking an API that's already used internally breaks the build everywhere it's referenced. Only add `[Experimental]` to a genuinely not-yet-stable API, and suppress every internal usage in the same change so the build stays green.
## `[Obsolete]` for deprecating public APIs
diff --git a/docs/agents/release-and-versioning.md b/docs/agents/release-and-versioning.md
index 4aca73cf..884bf27d 100644
--- a/docs/agents/release-and-versioning.md
+++ b/docs/agents/release-and-versioning.md
@@ -2,27 +2,29 @@
Branching, semver policy, the PR-creation procedure, and the release pipeline.
+> [!IMPORTANT]
+> **This document describes CURRENT STATE.** The project's North Star β calendar versioning and full GitFlow β is **not implemented**, and is recorded separately in [branching-and-release.md β North Star](../branching-and-release.md#north-star) and [versioning.md β North Star](../versioning.md#north-star). Do not write code, docs, or PR descriptions as though either is in force. See [ADR-0012](../adr/0012-current-state-semver-10x-north-star-calver-gitflow.md).
+
## Branching
-The branch/channel/versioning model is defined by [ADR-0004](../adr/0004-calendar-versioning-and-dual-pace-channels.md) (calendar versioning + dual-pace channels), as amended by [ADR-0008](../adr/0008-collapse-experimental-into-main.md) (which collapsed the `experimental` lane into `main` and retired the `-alpha` channel), and which amends [ADR-0001](../adr/0001-release-branch-model.md) (release-branch + tag-triggered multi-channel CD) and [ADR-0002](../adr/0002-v11-off-nuget-by-default.md) (nuget.org opt-in).
+The branch/channel/versioning model is defined by [ADR-0012](../adr/0012-current-state-semver-10x-north-star-calver-gitflow.md) (semver `10.x` now; CalVer + GitFlow as North Star), which defers [ADR-0004](../adr/0004-calendar-versioning-and-dual-pace-channels.md) Β§1. It builds on [ADR-0008](../adr/0008-collapse-experimental-into-main.md) (which collapsed the `experimental` lane into `main` and retired the `-alpha` channel), [ADR-0001](../adr/0001-release-branch-model.md) (release-branch + tag-triggered multi-channel CD), and [ADR-0002](../adr/0002-v11-off-nuget-by-default.md) (nuget.org opt-in).
-A two-tier maturity ladder (`main` β `release/YYYY`) feeding the production line. GitHub Packages = test/preview; nuget.org = production. Long-lived branches:
+A two-tier maturity ladder (`main` β `release/v.`) feeding the production line. GitHub Packages = test/preview; nuget.org = production. Long-lived branches:
-- `main` β the **integration trunk *and* the sole prerelease lane.** Default branch. **Both** deliberate improvements + bug fixes **and** faster/AI-assisted work land here. Every push publishes an NB.GV-native prerelease `YYYY.MINOR.PATCH-preview..g` (currently `10.5.0-preview..g`, on the 10.x line) to **GitHub Packages only β never nuget.org.** Ordinary review.
-- `release/YYYY` (e.g. `release/2026`) β the **production line** for the calendar year. **Cut from `main` on demand at the first release of the year, not preemptively** ([ADR-0007](../adr/0007-cut-release-branch-on-demand.md)); until then `main` (`-preview`) is the most-stable line. Hardened deliberately (slow crowd's domain, rigorous review), `-rc.N` β GA. After the cut it takes **non-breaking minors + patches only** β never a breaking change. Tag-triggered releases fire from here (the nuget.org tier). Protected per the policy below.
-- `support/v10` (+ `hotfix/v10.1`, `hotfix/v10.2`) β **legacy semver maintenance line**, `10.x`, **security and critical fixes only, no new features** (renamed from `release/v10`). Not renumbered into CalVer. Coexists indefinitely.
-- `support/YYYY` β a **retired** year production line (e.g. `support/2026` once 2027 supersedes it). Security/critical fixes only.
-- `release/v11` β **retired.** Nothing clean shipped under it (the `11.0.x` packages were unlisted); its rebrand/plugin work re-homed onto the `2026` line. Kept for archaeology, marked EoL β not a release target. Not renamed to `support/` (not a maintained line).
+- `main` β the **integration trunk *and* the sole prerelease lane.** Default branch. **Both** deliberate improvements + bug fixes **and** faster/AI-assisted work land here. Every push publishes an NB.GV-native prerelease `MAJOR.MINOR.PATCH-preview..g` (currently `10.5.0-preview..g`) to **GitHub Packages only β never nuget.org.** Ordinary review.
+- `release/v.` (currently `release/v10.4`; next `release/v10.5`) β the **production line**. **Cut from `main` on demand at the first release of the line, not preemptively** ([ADR-0007](../adr/0007-cut-release-branch-on-demand.md)); until then `main` (`-preview`) is the most-stable line. Hardened deliberately (slow crowd's domain, rigorous review), `-rc.N` β GA. After the cut it takes **non-breaking minors + patches only** β never a breaking change. Tag-triggered releases fire from here (the nuget.org tier). Protected per the policy below.
+- `support/v10` (+ `hotfix/v10.x`) β **legacy maintenance line**, pinned `"10.3"` with `versionHeightOffset: 24`, **security and critical fixes only, no new features** (renamed from `release/v10`). Coexists indefinitely and does **not** retire when a newer line is cut.
+- `release/v11` β **retired.** Nothing clean shipped under it (the `11.0.x` packages were unlisted); its rebrand/plugin work re-homed onto the `10.x` line. Not a release target. **`11.0.1`β`11.0.18` are burned** β nuget.org never frees a version that has existed, so that range can never be reused.
-Short-lived branches (rebase-merged via PR): `feature/`, `bugfix/`, `chore/`, `docs/`, `pr/-`. They target `main`. Breaking work that cannot be gated behind `[Experimental("FALLOUT0xx")]` waits for the year cut on a short-lived topic branch off `main`.
+Short-lived branches (rebase-merged via PR): `feature/`, `bugfix/`, `chore/`, `docs/`, `pr/-`. They target `main`. Breaking work that cannot be gated behind `[Experimental("FALLOUT0xx")]` waits for the next major cut on a short-lived topic branch off `main`.
-No `develop` (literal) or `master` branches. The ladder flows **forward-only**: `main β release/YYYY`. The `support/*` lines are maintenance-only β security/critical fixes land via a PR targeting (or cherry-pick to) `support/v10` / `support/YYYY` (or the relevant `hotfix/v10.x`) and are tagged from there.
+No `develop` (literal) or `master` branches **today** β note that the GitFlow North Star would introduce `develop`, but it is not implemented. The ladder flows **forward-only**: `main β release/v.`. The `support/*` lines are maintenance-only β security/critical fixes land via a PR targeting (or cherry-pick to) `support/v10` (or the relevant `hotfix/v10.x`) and are tagged from there.
CI providers in use: **GitHub Actions only** (others were dropped β see [#8](https://github.com/Fallout-build/Fallout/issues/8) for the demand-driven revival roadmap).
-### Branch protection on `release/YYYY` and `support/*`
+### Branch protection on `release/*` and `support/*`
-`main`, every release line, and every `support/*` branch share the same protection profile:
+`main`, every release line, and every `support/*` branch share `main`'s protection profile:
- Required status check: `ubuntu-latest`
- Linear history required (no merge commits)
@@ -47,22 +49,24 @@ Tag protection for `v*` tags is a separate ruleset ([17017817](https://github.co
## Versioning
-**Calendar versioning: `YYYY.MINOR.PATCH`** (see [ADR-0004](../adr/0004-calendar-versioning-and-dual-pace-channels.md), as amended by [ADR-0008](../adr/0008-collapse-experimental-into-main.md)). It is mechanically valid SemVer 2.0 β all three components are numeric β so [Nerdbank.GitVersioning](https://github.com/dotnet/Nerdbank.GitVersioning), NuGet, and version ordering all work unchanged. The major *is* the calendar year.
+**Semantic versioning on the `10.x` line.** Full mechanics β git height, `-rc.N` pinning, and the two traps that have shipped bugs β are in **[docs/versioning.md](../versioning.md)**. Read that before touching `version.json` or cutting a release. The essentials:
-- **`MAJOR` = year**, hand-set in `version.json` at the yearly cut. **`MINOR`** = feature drop within the year. **`PATCH`** = git-height fixes.
-- Per-branch via `version.json`. The preview lane is a **non-public ref** carrying the next planned version with a prerelease tag: `main` β `"10.5.0-preview.{height}"` (`firstUnstableTag` is `preview`). Each `release/YYYY` carries `"version": "YYYY.x"`; the current `release/v10.4` pins its prerelease literally (`"version": "10.4.0-rc.N"` β a manual counter, see the runbook); `support/v10` keeps `"version": "10.x"`; `support/YYYY` keeps `"version": "YYYY.x"`. `publicReleaseRefSpec` matches the four production patterns: `^refs/heads/release/\d{4}$`, `^refs/heads/release/v\d+\.\d+$`, `^refs/heads/support/\d{4}$`, `^refs/heads/support/v\d+$` (**not** `main`).
-- Preview-lane builds carry the height + commit in the **prerelease segment** (`10.5.0-preview..g`), never the version core β a core like `2026.05.29` would parse as a *stable* release, not a nightly. `main` is a non-public ref, so NB.GV appends the `.g` suffix. The ladder orders cleanly: `-preview` < `-rc` < GA.
+- Per-branch via `version.json`. The preview lane is a **non-public ref** carrying the next planned version with a prerelease tag: `main` β `"10.5.0-preview.{height}"` (`firstUnstableTag` is `preview`). A release branch pins the **full** version literally, prerelease segment included β `"10.4.0-rc.4"`, then `"10.4.0"` at GA. `support/v10` carries `"10.3"` + `versionHeightOffset: 24`.
+- `publicReleaseRefSpec` matches the production branch patterns (**not** `main`). It also retains `\d{4}` CalVer patterns that match nothing today β harmless, and they mean a future CalVer adoption needs no change to that field.
+- Preview-lane builds carry the height + commit in the **prerelease segment** (`10.5.0-preview..g`), never the version core. `main` is a non-public ref, so NB.GV appends the `.g` suffix. The ladder orders cleanly: `-preview` < `-rc` < GA.
+- **`{height}` is automatic and preview-only.** It resets whenever the `version` field changes. `-rc.N` is bumped by hand β driving it from height made the counter track promotion size, sending `rc.3` to `rc.23`.
+- **Two traps** (both have shipped bugs β see [versioning.md](../versioning.md)): `main`'s preview core must be rolled forward in the same sitting as a release cut, or previews strand below the shipped release; and tag builds run on a detached HEAD that matches no `publicReleaseRefSpec` entry, which is why the release workflow sets `PublicRelease: true`.
-GitVersion is still installed as a transitional helper for `MajorMinorPatchVersion` in `Build.cs`; full removal is a follow-up.
+Versioning is Nerdbank.GitVersioning only β GitVersion is no longer referenced.
## Versioning policy
-This project ships calendar versions that are valid [Semantic Versioning](https://semver.org/spec/v2.0.0.html). The rule is: **breaking changes are batched to the yearly major cut.**
+This project ships [Semantic Versioning](https://semver.org/spec/v2.0.0.html). The rule is: **breaking changes are batched to the next major cut.**
There is **no `CHANGELOG.md`** β the file was retired. Release notes are generated from PR labels via [`.github/release.yml`](https://github.com/Fallout-build/Fallout/blob/main/.github/release.yml), so the PR description and its labels are the durable record of a change.
-- A breaking change lands on **`main`, gated behind `[Experimental("FALLOUT0xx")]`** (or, when it can't be gated, on a short-lived topic branch off `main` held until the cut), is held for the next yearly major (it does **not** bump `version.json`'s major mid-year), and describes its migration path in the PR description under a `β οΈ Breaking change` callout.
-- **A `release/YYYY` production line never takes a breaking change** β it's strictly non-breaking (minor = features, patch = fixes). The production-cut review is the backstop that keeps ungated breaking work off the production line (ADR-0008).
+- A breaking change lands on **`main`, gated behind `[Experimental("FALLOUT0xx")]`** (or, when it can't be gated, on a short-lived topic branch off `main` held until the cut), is held for the next major (it does **not** bump `version.json`'s major mid-line), and describes its migration path in the PR description under a `β οΈ Breaking change` callout.
+- **A `release/v.` production line never takes a breaking change** β it's strictly non-breaking (minor = features, patch = fixes). The production-cut review is the backstop that keeps ungated breaking work off the production line (ADR-0008).
- Surface that isn't ready to commit to can ship behind `[Experimental("FALLOUT0xx")]` instead of being held back β opt-in for consumers, and not a breaking change to add or remove. With the `experimental` branch retired, the attribute is now the primary per-API isolation tool.
A "breaking change" is any of:
@@ -71,11 +75,11 @@ A "breaking change" is any of:
- A `BREAKING CHANGE:` footer in the commit body.
- A change a reviewer reasonably flags as breaking even without the marker (renamed/removed public API, package ID change, on-disk format change, CI/CD shape change consumers depend on) β **except** changes to `[Experimental]` surface, which carry no stability guarantee.
-**Reviewer responsibility:** if a PR carries `!` (or a flagged breaking change), confirm it targets `main` (not a production train), that the breaking surface is gated behind `[Experimental("FALLOUT0xx")]` (or held on a topic branch when it can't be gated), and that the PR description carries the `β οΈ Breaking change` callout with a migration path. Block otherwise. The production-cut review is the backstop for any ungated breaking change reaching a `release/YYYY` cut.
+**Reviewer responsibility:** if a PR carries `!` (or a flagged breaking change), confirm it targets `main` (not a production train), that the breaking surface is gated behind `[Experimental("FALLOUT0xx")]` (or held on a topic branch when it can't be gated), and that the PR description carries the `β οΈ Breaking change` callout with a migration path. Block otherwise. The production-cut review is the backstop for any ungated breaking change reaching a production cut.
## Milestones and version targeting
-Milestones are **theme-based** (e.g. "Plugin Architecture Foundation & Rebrand Completion", "Public Plugin SDK", "Continuous Delivery Vision") and carry across releases; version targeting uses **evergreen `target/vCurrent` / `target/vNext`** labels β `target/vCurrent` is the current release line, `target/vNext` is next year's major. A breaking change is held for the next yearly major β so its PR carries `target/vNext`.
+Milestones are **theme-based** (e.g. "Plugin Architecture Foundation & Rebrand Completion", "Public Plugin SDK", "Continuous Delivery Vision") and carry across releases; version targeting uses **evergreen `target/vCurrent` / `target/vNext`** labels β `target/vCurrent` is the current release line, `target/vNext` is the next major. A breaking change is held for the next major β so its PR carries `target/vNext`.
## PR-creation flow
@@ -85,21 +89,21 @@ not as a follow-up β every PR gets:
0. **Working from a fork? Branch from `upstream/main`, push to `origin`, PR against `upstream`.** If `git remote -v` shows both an `origin` (a personal fork, e.g. `/Fallout`) and an `upstream` remote pointing at `Fallout-build/Fallout`, treat `upstream` as the core repo: `git fetch upstream main` and branch from `upstream/main` (never `origin/main`, which can be arbitrarily stale relative to the core repo and will produce huge, spurious merge conflicts), push the new branch to `origin`, and open the PR against `upstream` β `gh pr create --repo Fallout-build/Fallout --draft ...`. Do this by default unless the user explicitly asks to branch off the fork's own `main` instead. Doesn't apply to a plain single-remote clone.
1. **Create the PR as a draft** β `gh pr create --draft` (see [issue-and-pr-style.md](issue-and-pr-style.md#pr-description-shape)) unless the user explicitly asks for a ready-for-review PR. This is easy to miss because it's a small flag on the same `gh pr create` call as the labels below β don't drop it.
-2. **A `target/vCurrent` or `target/vNext` label** matching where it will release. Default to `target/vCurrent` (the current release line). If the PR carries a breaking change, it's held for the next yearly major β use `target/vNext`. Pass via `--label target/vCurrent` to `gh pr create`.
+2. **A `target/vCurrent` or `target/vNext` label** matching where it will release. Default to `target/vCurrent` (the current release line). If the PR carries a breaking change, it's held for the next major β use `target/vNext`. Pass via `--label target/vCurrent` to `gh pr create`.
3. **A changelog-category label** describing the change, from [`.github/release.yml`](../../.github/release.yml) β that file is the source of truth for the taxonomy and carries a one-line blurb on each label. Apply the one category the PR belongs under (`enhancement`, `bug`, `security`, `documentation`; `breaking-change` when it applies β see below), or `skip-changelog` for housekeeping with no release note. Pass it in the same `gh pr create --label β¦` call. Don't leave a PR uncategorized β it falls through to "Other Changes". This is the labelling AI applies on the user's behalf whenever it raises a PR.
If the PR includes a **breaking change** (any commit uses `!`, has a `BREAKING CHANGE:` footer, or otherwise meets the breaking-change definition above), additionally:
4. **Add the `breaking-change` label** (this is its changelog category β use it instead of `enhancement`/`bug`). `gh pr create --label target/vNext --label breaking-change β¦`.
5. **Open the PR body with a `β οΈ Breaking change` callout** that names the affected surface (public API, package ID, CLI flag, on-disk format, CI/CD shape, etc.) and the consumer-side impact in one sentence. This is what reviewers and downstream consumers read first.
-6. **Confirm the PR targets `main`, not a `release/YYYY` production train, and that the breaking surface is gated behind `[Experimental("FALLOUT0xx")]`** (or, when it can't be gated, lives on a short-lived topic branch off `main` held until the year cut). Breaking changes accumulate on `main` for the next yearly major; they may not land on a production train. (Do **not** bump `version.json`'s major in the PR β the major is set once, at the yearly cut.)
+6. **Confirm the PR targets `main`, not a `release/v.` production train, and that the breaking surface is gated behind `[Experimental("FALLOUT0xx")]`** (or, when it can't be gated, lives on a short-lived topic branch off `main` held until the cut). Breaking changes accumulate on `main` for the next major; they may not land on a production train. (Do **not** bump `version.json`'s major in the PR β the major is set once, at the cut.)
7. **Spell out the migration path in the PR description** (one paragraph minimum) β what a consumer has to change, and what to run. The `breaking-change` label carries it into the generated release notes; there is no `CHANGELOG.md` to record it in.
If you only discover the breaking nature mid-review, apply all relevant steps before requesting re-review.
## Release pipeline
-`.github/workflows/publish-packages-release.yml` is **tag-triggered**: pushing a `v*` tag on a production branch (`release/YYYY`, `release/vMAJOR.MINOR`, or `support/*`) fires the pipeline. The workflow validates the tag is reachable from such a branch, then fans out a Test+Pack job to three parallel publish jobs:
+`.github/workflows/publish-packages-release.yml` is **tag-triggered**: pushing a `v*` tag on a production branch (`release/v.` or `support/*`) fires the pipeline. The workflow validates the tag is reachable from such a branch, then fans out a Test+Pack job to three parallel publish jobs:
| Job | Environment | Fires on tag push? | What ships | Gating |
|---|---|---|---|---|
@@ -113,7 +117,7 @@ Pushes to `main` publish **preview prereleases** (`YYYY.MINOR.PATCH-preview.
Date: Sun, 16 Aug 2026 12:54:34 +1200
Subject: [PATCH 09/10] Correct release/YYYY references in the
contributor-facing docs
CONTRIBUTING.md and the README described stable releases shipping from
calendar-year branches. Restates them on release/v. so a
first-time contributor is not told to target a branch pattern that has
never existed.
Co-Authored-By: Claude Opus 5 (1M context)
---
CONTRIBUTING.md | 12 ++++++------
README.md | 2 +-
2 files changed, 7 insertions(+), 7 deletions(-)
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
index 0c3c3fd1..95cdfc08 100644
--- a/CONTRIBUTING.md
+++ b/CONTRIBUTING.md
@@ -8,7 +8,7 @@ Fallout welcomes contributions. As a community, we want to help each other, prov
- Discuss non-trivial changes in an [issue](https://github.com/Fallout-build/Fallout/issues) first.
- Small fixes (typos, broken links, tool wrapper additions) can go straight to a PR against `main`.
-- **`main` feeds the production line.** `main` is the **integration trunk + sole `-preview` channel** β the default branch where both deliberate improvements/bug fixes *and* faster work land. Breaking changes also land on `main`, gated behind `[Experimental("FALLOUT0xx")]` (or held on a short-lived topic branch off `main` when they can't be gated) and batched to the yearly major. **Stable releases ship from `release/YYYY` branches** (the calendar-year production line, the nuget.org tier), and the legacy `support/v10` line takes security/critical fixes only (see [Branching and release flow](docs/branching-and-release.md) and [ADR-0004](docs/adr/0004-calendar-versioning-and-dual-pace-channels.md), as amended by [ADR-0008](docs/adr/0008-collapse-experimental-into-main.md), for the full model). **Branch from, and PR against, `main`.** The only time you target a production branch directly is for a maintainer-driven hotfix.
+- **`main` feeds the production line.** `main` is the **integration trunk + sole `-preview` channel** β the default branch where both deliberate improvements/bug fixes *and* faster work land. Breaking changes also land on `main`, gated behind `[Experimental("FALLOUT0xx")]` (or held on a short-lived topic branch off `main` when they can't be gated) and batched to the next major. **Stable releases ship from `release/v.` branches** (currently `release/v10.4`, the nuget.org tier), and the legacy `support/v10` line takes security/critical fixes only (see [Branching and release flow](docs/branching-and-release.md) and [ADR-0012](docs/adr/0012-current-state-semver-10x-north-star-calver-gitflow.md) for the full model β note that calendar versioning and GitFlow are the project's **North Star**, not current state). **Branch from, and PR against, `main`.** The only time you target a production branch directly is for a maintainer-driven hotfix.
## Baseline contributions
@@ -55,7 +55,7 @@ Fallout welcomes contributions. As a community, we want to help each other, prov
- There's no committed `.editorconfig` or ReSharper/`*.DotSettings` file β they were removed during the takeover. Rely on `dotnet format` defaults and review; don't reintroduce them without a maintainer-level decision.
- Add tests when meaningful β every `Foo` project has a sibling `Foo.Tests`.
- Commit the regenerated `.cs` output alongside the `.json` spec β `VerifyGeneratedTools` fails CI if they drift.
-- **Label the PR `target/vCurrent`** for the current release line (use `target/vNext` for work held to next year's major). **Breaking changes are batched to the yearly major cut**: they land on `main` gated behind `[Experimental("FALLOUT0xx")]` (or, when they can't be gated, on a short-lived topic branch off `main`) β never on a `release/YYYY` production train β are held for next year's `YYYY+1.0.0`, and additionally get a `breaking-change` label plus a `β οΈ Breaking change` callout in the PR description naming the migration path. Surface that isn't ready to commit to can ship behind `[Experimental("FALLOUT0xx")]` instead of being held back. See the [PR-creation flow](docs/agents/release-and-versioning.md#pr-creation-flow) for the full procedure.
+- **Label the PR `target/vCurrent`** for the current release line (use `target/vNext` for work held to the next major). **Breaking changes are batched to the next major cut**: they land on `main` gated behind `[Experimental("FALLOUT0xx")]` (or, when they can't be gated, on a short-lived topic branch off `main`) β never on a `release/v.` production train β and additionally get a `breaking-change` label plus a `β οΈ Breaking change` callout in the PR description naming the migration path. Surface that isn't ready to commit to can ship behind `[Experimental("FALLOUT0xx")]` instead of being held back. See the [PR-creation flow](docs/agents/release-and-versioning.md#pr-creation-flow) for the full procedure.
### Tool wrappers
@@ -77,7 +77,7 @@ Tool wrapper JSON lives under `src/Fallout.Common/Tools//.json`. Whe
### After opening a PR
- The PR gate is the `ubuntu-latest` job (from `build.yml`) only β fires on PRs against `main`, `release/*`, or `support/*`. Docs-only PRs hit a no-op shim workflow (`build-skip.yml`) that reports the same status check name. `build-cross-platform.yml` runs Windows + macOS validation on `release/*` / `support/*` PRs and `v*` tag pushes (gated to release intent), not on routine `main` work.
-- **Review rises with the ladder.** PRs to `main` (preview) get ordinary review β it's the integration trunk. Promotion to a `release/YYYY` production train (and the GA cut) gets rigorous, unhurried review β that's the project's quality gate. Match your expectations to where the PR is headed.
+- **Review rises with the ladder.** PRs to `main` (preview) get ordinary review β it's the integration trunk. Promotion to a production train (and the GA cut) gets rigorous, unhurried review β that's the project's quality gate. Match your expectations to where the PR is headed.
- Address review feedback in additional commits rather than force-pushing β easier to review the changes.
- If CI fails on something unrelated to your change, ping a maintainer.
@@ -92,11 +92,11 @@ The merger (typically a CODEOWNER) clicks **Rebase and merge** β there's no bu
## Releases
-Merging to `main` publishes a **preview prerelease** (`2026.MINOR.PATCH-preview.β¦`) to **GitHub Packages only** β never nuget.org. This is the preview lane. **Stable releases** fire from `release/YYYY` branches via tag push, with a multi-channel publish fan-out (GitHub Packages + GitHub Releases by default; nuget.org is **opt-in**). The full lifecycle is documented in [docs/branching-and-release.md](docs/branching-and-release.md):
+Merging to `main` publishes a **preview prerelease** (currently `10.5.0-preview.β¦`) to **GitHub Packages only** β never nuget.org. This is the preview lane. **Stable releases** fire from `release/v.` branches via tag push, with a multi-channel publish fan-out (GitHub Packages + GitHub Releases by default; nuget.org is **opt-in**). The full lifecycle is documented in [docs/branching-and-release.md](docs/branching-and-release.md):
-- How releases happen (tag a `release/YYYY` branch, parallel publish jobs)
+- How releases happen (tag a `release/v.` branch, parallel publish jobs)
- The channel taxonomy (preview β GitHub Packages; stable β GitHub Packages + GitHub Releases, nuget.org opt-in; Docker local for pre-merge)
-- Promotion + hotfix flow (forward-only `main β release/YYYY`; the legacy `support/v10` line takes security/critical fixes directly)
+- Promotion + hotfix flow (forward-only `main β release/v.`; the legacy `support/v10` line takes security/critical fixes directly)
- When to cut a new year
Contributors don't usually need to do any of this β releases are maintainer-driven. But if you're filing a fix for the legacy `support/v10` line, or one that carries a breaking change held for next year's major, expect the maintainer to route it accordingly.
diff --git a/README.md b/README.md
index 9e9714ac..5f5d569d 100644
--- a/README.md
+++ b/README.md
@@ -70,7 +70,7 @@ The promise of Fallout, just like its predecessor NUKE, is to deliver a C#-based
|---|---|---|
| [`build`](.github/workflows/build.yml) | [](https://github.com/Fallout-build/Fallout/actions/workflows/build.yml) | PR to `main` / `release/*` / `support/*` (code paths) β job `ubuntu-latest` is the **required check** |
| [`publish-packages-preview`](.github/workflows/publish-packages-preview.yml) | [](https://github.com/Fallout-build/Fallout/actions/workflows/publish-packages-preview.yml) | push to `main` β `β¦-preview` prerelease to GitHub Packages |
-| [`publish-packages-release`](.github/workflows/publish-packages-release.yml) | [](https://github.com/Fallout-build/Fallout/actions/workflows/publish-packages-release.yml) | tag push on `release/YYYY` (stable) or `support/*` (legacy/retired) β nuget.org opt-in |
+| [`publish-packages-release`](.github/workflows/publish-packages-release.yml) | [](https://github.com/Fallout-build/Fallout/actions/workflows/publish-packages-release.yml) | tag push on `release/v*` (stable) or `support/*` (legacy) β nuget.org opt-in |
Multi-provider CI support (Azure Pipelines, GitLab, TeamCity, AppVeyor) was removed during the takeover and is being revived demand-driven β see [#8](https://github.com/Fallout-build/Fallout/issues/8).
From d13256abf3cb834c01e7bc8eb235d54a6630aecc Mon Sep 17 00:00:00 2001
From: Chrison Simtian
Date: Sun, 16 Aug 2026 12:54:34 +1200
Subject: [PATCH 10/10] Fix stale CalVer examples in the publish workflow
headers
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
Both headers documented a 2026.1.0 version shape and release/YYYY trigger
branches. Comment-only β the validate-ref regex already matched the
release/v. branches actually in use, and its \d{4}
alternatives are retained for the CalVer North Star.
Co-Authored-By: Claude Opus 5 (1M context)
---
.github/workflows/publish-packages-preview.yml | 4 ++--
.github/workflows/publish-packages-release.yml | 18 +++++++++---------
2 files changed, 11 insertions(+), 11 deletions(-)
diff --git a/.github/workflows/publish-packages-preview.yml b/.github/workflows/publish-packages-preview.yml
index 87aed310..d98723d0 100644
--- a/.github/workflows/publish-packages-preview.yml
+++ b/.github/workflows/publish-packages-preview.yml
@@ -1,9 +1,9 @@
-# Hand-written. The "preview" channel β `main` (ADR-0004, amended 2026-05-30).
+# Hand-written. The "preview" channel β `main` (ADR-0004 as deferred by ADR-0012).
#
# Shape:
# - Trigger: push to `main`. `main` is the integration trunk / `-preview`
# channel β the deliberate lane where work stabilises. Nerdbank.GitVersioning
-# produces a prerelease of the form `2026.1.0-preview..g`
+# produces a prerelease of the form `10.5.0-preview..g`
# (`main` is intentionally NOT a public-release ref in version.json), so these
# never look like a stable release.
# - Builds + packs, then pushes every *.nupkg to GitHub Packages ONLY.
diff --git a/.github/workflows/publish-packages-release.yml b/.github/workflows/publish-packages-release.yml
index 65ed9a8c..062f5be2 100644
--- a/.github/workflows/publish-packages-release.yml
+++ b/.github/workflows/publish-packages-release.yml
@@ -9,10 +9,10 @@
#
# Shape:
# - Trigger: tag push `v*` on a production branch β CalVer production lines
-# (release/2026 β tag v2026.1.3) or gitflow support lines (support/v10 β v10.x,
-# support/2026 β v2026.x). main is NEVER tagged for release.
+# (release/v10.5 β tag v10.5.3) or legacy support lines (support/v10 β v10.x).
+# main is NEVER tagged for release.
# Tags keep the `v` prefix so the `v*` tag-protection ruleset applies; the
-# package version core is the bare CalVer/semver (e.g. 2026.1.3). Tag
+# package version core is the bare semver (e.g. 10.5.3). Tag
# protection 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, OR to opt into a
@@ -26,7 +26,7 @@
# - nuget.org is OPT-IN. Tag pushes do NOT auto-publish there. To publish
# Fallout.* to nuget.org you invoke workflow_dispatch with
# publish-to-nugetorg=true β a conscious "this release is stabilised enough"
-# switch, used when a release/YYYY production line is ready for the broad
+# switch, used when a production line is ready for the broad
# consumer audience, or for a support/v10 legacy security patch.
# - GitHub Packages gets every Fallout.* and Nuke.* build via tag push.
# - GitHub Releases bundles nupkgs on the tag's release page regardless.
@@ -78,13 +78,13 @@ jobs:
run: |
set -euo pipefail
TAG_SHA="${{ github.sha }}"
- # Match CalVer production lines (release/2026, β¦), legacy-style minor
- # release lines (release/v10.4, β¦), and gitflow support lines
- # (support/v10 legacy semver, support/2026 retired years, β¦).
+ # Match production lines (release/v10.4, β¦) and legacy support lines
+ # (support/v10, β¦). The \d{4} alternatives are retained for the CalVer
+ # North Star (ADR-0012) and match nothing today.
REACHABLE=$(git branch -r --contains "$TAG_SHA" | grep -E 'origin/(release/(v[0-9]+\.[0-9]+|[0-9]{4})|support/(v[0-9]+|[0-9]{4}))' || true)
if [ -z "$REACHABLE" ]; then
echo "::error::Tag ${GITHUB_REF_NAME} at $TAG_SHA is not reachable from any production branch."
- echo "Tag-triggered releases only fire from release/YYYY (production) or support/* (legacy/retired) branches."
+ echo "Tag-triggered releases only fire from release/* (production) or support/* (legacy) branches."
exit 1
fi
echo "Tag ${GITHUB_REF_NAME} validated. Reachable from:"
@@ -139,7 +139,7 @@ jobs:
# in via the input flag, you still approve the deployment.
#
# The opt-in defaults to false because the default channel is GitHub Packages
- # (see header comment). When a release/YYYY production line is ready for the
+ # (see header comment). When a production line is ready for the
# broad audience β or for cutting a support/v10 maintenance patch β invoke
# workflow_dispatch with the flag set.
#