diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index 3b08bb53b..713f0c064 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -8,5 +8,5 @@ # changelogs, etc.) have no code owner and are gated only by the required CI # status check. -/src/** @ChrisonSimtian -/tests/** @ChrisonSimtian +/src/** @Fallout-build/maintainers +/tests/** @Fallout-build/maintainers diff --git a/.github/workflows/experimental.yml b/.github/workflows/experimental.yml index 76faad6d7..8307489d1 100644 --- a/.github/workflows/experimental.yml +++ b/.github/workflows/experimental.yml @@ -28,6 +28,11 @@ on: - '.assets/**' - '**/*.md' +# Cancel a superseded alpha build when a newer commit lands (#322). Never on release.yml. +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + permissions: contents: read @@ -44,7 +49,6 @@ jobs: - uses: actions/checkout@v6 with: fetch-depth: 0 # Nerdbank.GitVersioning needs full history - submodules: recursive - name: 'Cache: .fallout/temp, ~/.nuget/packages' uses: actions/cache@v4 with: @@ -52,27 +56,21 @@ jobs: .fallout/temp ~/.nuget/packages key: ${{ runner.os }}-experimental-${{ hashFiles('**/global.json', '**/*.csproj', '**/Directory.Packages.props', 'version.json') }} + restore-keys: | + ${{ runner.os }}-experimental- - name: 'Setup: .NET SDK' uses: actions/setup-dotnet@v4 with: global-json-file: global.json - name: 'Restore: dotnet tools' run: dotnet tool restore - - name: 'Run: Pack' - run: dotnet fallout Pack - - name: 'Push: all *.nupkg to GitHub Packages (alpha)' - run: | - set -euo pipefail - shopt -s nullglob - packages=(output/packages/*.nupkg) - if [ ${#packages[@]} -eq 0 ]; then - echo "::error::No packages found — nothing to publish to the experimental channel." - exit 1 - fi - for pkg in "${packages[@]}"; do - echo "Pushing $pkg to GitHub Packages (alpha)..." - dotnet nuget push "$pkg" \ - --source "https://nuget.pkg.github.com/ChrisonSimtian/index.json" \ - --api-key "${{ secrets.GITHUB_TOKEN }}" \ - --skip-duplicate - done + # Test + Pack + push alpha → GitHub Packages, all through our own framework (#333) — + # dogfooding `dotnet fallout Publish` instead of a hand-rolled `dotnet nuget push`. + # `Publish` depends on Test + Pack, so NUKE runs Restore → Compile → Test → Pack → + # Publish as discrete internal stages and fails at the breaking stage; if Test fails + # the job stops before any push. Which packages go where (Fallout.* + Nuke.* → GitHub + # Packages) is decided by Build.cs IPublish.PublishTargets, not here. + - name: 'Publish: alpha → GitHub Packages' + run: dotnet fallout Publish --publish-to github-packages + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} diff --git a/.github/workflows/macos-latest.yml b/.github/workflows/macos-latest.yml index b17aacacd..426d42ffa 100644 --- a/.github/workflows/macos-latest.yml +++ b/.github/workflows/macos-latest.yml @@ -18,11 +18,20 @@ name: macos-latest on: push: + tags: + - 'v*' + pull_request: branches: - - experimental - - main - 'release/*' - 'support/*' + paths-ignore: + - 'docs/**' + - '.assets/**' + - '**/*.md' + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true jobs: macos-latest: @@ -31,7 +40,6 @@ jobs: steps: - uses: actions/checkout@v6 with: - submodules: recursive fetch-depth: 0 - name: 'Cache: .fallout/temp, ~/.nuget/packages' uses: actions/cache@v4 diff --git a/.github/workflows/preview.yml b/.github/workflows/preview.yml index 5b90bedfa..2045a707f 100644 --- a/.github/workflows/preview.yml +++ b/.github/workflows/preview.yml @@ -28,6 +28,11 @@ on: - '.assets/**' - '**/*.md' +# Cancel a superseded preview build when a newer commit lands (#322). Never on release.yml. +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + permissions: contents: read @@ -44,7 +49,6 @@ jobs: - uses: actions/checkout@v6 with: fetch-depth: 0 # Nerdbank.GitVersioning needs full history - submodules: recursive - name: 'Cache: .fallout/temp, ~/.nuget/packages' uses: actions/cache@v4 with: @@ -52,27 +56,21 @@ jobs: .fallout/temp ~/.nuget/packages key: ${{ runner.os }}-preview-${{ hashFiles('**/global.json', '**/*.csproj', '**/Directory.Packages.props', 'version.json') }} + restore-keys: | + ${{ runner.os }}-preview- - name: 'Setup: .NET SDK' uses: actions/setup-dotnet@v4 with: global-json-file: global.json - name: 'Restore: dotnet tools' run: dotnet tool restore - - name: 'Run: Pack' - run: dotnet fallout Pack - - name: 'Push: all *.nupkg to GitHub Packages (preview)' - run: | - set -euo pipefail - shopt -s nullglob - packages=(output/packages/*.nupkg) - if [ ${#packages[@]} -eq 0 ]; then - echo "::error::No packages found — nothing to publish to the preview channel." - exit 1 - fi - for pkg in "${packages[@]}"; do - echo "Pushing $pkg to GitHub Packages (preview)..." - dotnet nuget push "$pkg" \ - --source "https://nuget.pkg.github.com/ChrisonSimtian/index.json" \ - --api-key "${{ secrets.GITHUB_TOKEN }}" \ - --skip-duplicate - done + # Test + Pack + push preview → GitHub Packages, all through our own framework (#333) — + # dogfooding `dotnet fallout Publish` instead of a hand-rolled `dotnet nuget push`. + # `Publish` depends on Test + Pack, so NUKE runs Restore → Compile → Test → Pack → + # Publish as discrete internal stages and fails at the breaking stage; if Test fails + # the job stops before any push. Which packages go where (Fallout.* + Nuke.* → GitHub + # Packages) is decided by Build.cs IPublish.PublishTargets, not here. + - name: 'Publish: preview → GitHub Packages' + run: dotnet fallout Publish --publish-to github-packages + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index d7820252e..10e1e1e4d 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -94,7 +94,6 @@ jobs: with: ref: ${{ inputs.tag || github.ref }} fetch-depth: 0 # Nerdbank.GitVersioning needs full history - submodules: recursive # vendor/vs-solutionpersistence - name: 'Cache: .fallout/temp, ~/.nuget/packages' uses: actions/cache@v4 with: @@ -102,6 +101,8 @@ jobs: .fallout/temp ~/.nuget/packages key: ${{ runner.os }}-release-${{ hashFiles('**/global.json', '**/*.csproj', '**/Directory.Packages.props', 'version.json') }} + restore-keys: | + ${{ runner.os }}-release- - name: 'Setup: .NET SDK' uses: actions/setup-dotnet@v4 with: @@ -198,7 +199,7 @@ jobs: for pkg in "${packages[@]}"; do echo "Pushing $pkg to GitHub Packages..." dotnet nuget push "$pkg" \ - --source "https://nuget.pkg.github.com/ChrisonSimtian/index.json" \ + --source "https://nuget.pkg.github.com/Fallout-build/index.json" \ --api-key "${{ secrets.GITHUB_TOKEN }}" \ --skip-duplicate done diff --git a/.github/workflows/ubuntu-latest.yml b/.github/workflows/ubuntu-latest.yml index e1e942332..45f548d6e 100644 --- a/.github/workflows/ubuntu-latest.yml +++ b/.github/workflows/ubuntu-latest.yml @@ -28,6 +28,10 @@ on: - '.assets/**' - '**/*.md' +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + jobs: ubuntu-latest: name: ubuntu-latest @@ -35,7 +39,6 @@ jobs: steps: - uses: actions/checkout@v6 with: - submodules: recursive fetch-depth: 0 repository: ${{ github.event.pull_request.head.repo.full_name || github.repository }} ref: ${{ github.head_ref }} diff --git a/.github/workflows/windows-latest.yml b/.github/workflows/windows-latest.yml index abfd9026f..bc97f4c73 100644 --- a/.github/workflows/windows-latest.yml +++ b/.github/workflows/windows-latest.yml @@ -18,11 +18,20 @@ name: windows-latest on: push: + tags: + - 'v*' + pull_request: branches: - - experimental - - main - 'release/*' - 'support/*' + paths-ignore: + - 'docs/**' + - '.assets/**' + - '**/*.md' + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true jobs: windows-latest: @@ -31,7 +40,6 @@ jobs: steps: - uses: actions/checkout@v6 with: - submodules: recursive fetch-depth: 0 - name: 'Cache: .fallout/temp, ~/.nuget/packages' uses: actions/cache@v4 diff --git a/CHANGELOG.md b/CHANGELOG.md index 8ac51bd0f..41a16da0b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,6 +11,9 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0. - **Adopted calendar versioning (`YYYY.MINOR.PATCH`) + dual-pace channel model; retired the v11 numbering** ([ADR-0004](docs/adr/0004-calendar-versioning-and-dual-pace-channels.md)). Fallout now ships on calendar versions (`2026.0.0`, `2026.1.0`, …) — mechanically valid SemVer with the major equal to the calendar year. **Breaking changes are batched to the yearly major cut**; mid-year stable releases (`release/YYYY`) are strictly non-breaking. `main` becomes the published **edge** channel (date-stamped prereleases to GitHub Packages); the slow/stable track lives on `release/YYYY`. Opt-in unstable APIs are marked `[Experimental("FALLOUT0xx")]`. - **Migration / impact**: the `11.0.x` packages never shipped a clean stable release (all unlisted), so this strands no stable consumers. The headline content previously slated for "v11" now ships as **`2026.0.0`**. Any tooling pinned to a `[11.0,12.0)`-style range should retarget the `2026.x` line. - **Legacy unaffected**: the `release/v10` line stays on semver `10.x` and continues to receive security/critical fixes — v10 consumers do nothing. +- **Renamed public type `MSBuildSettings` → `MsBuildSettings`** for casing consistency with the rest of the API surface (PascalCase acronym: `MsBuild`, not `MSBuild`). The MSBuild tool wrapper's settings type, its extension methods (`Fallout.Common.Tools.MSBuild.MsBuildSettingsExtensions`, `Fallout.Common.CI.TeamCity.MSBuildSettingsExtensions`), and `MSBuildTasks` all reference the new name. + - **Migration / impact**: replace `MSBuildSettings` with `MsBuildSettings` in build code (e.g. `MSBuild(_ => _.Set…())` lambdas are unaffected — they infer the settings type; only explicit `MSBuildSettings`-typed locals/fields/configurators need updating). The `DotNetMSBuildSettings` type (the `dotnet msbuild` wrapper) is unrelated and **not** renamed. + - **No shim**: this is a hard rename with no `[Obsolete]` alias — it rides `experimental` and is batched to the major cut rather than landing mid-year. - **Inlined `vs-solutionpersistence` parser + renamed `Fallout.SolutionModel` → `Fallout.Solution`; namespace `Fallout.Common.ProjectModel` → `Fallout.Solutions`** (closes #248). The vendored Microsoft fork (`vendor/vs-solutionpersistence/`, submodule, fork-of-fork-of-Microsoft) is gone — sources inlined into a new `Fallout.Persistence.Solution` project under `src/Persistence/`. The facade was renamed and rehoused alongside it (`src/Persistence/Fallout.Solution/`), with the long-standing rebrand-era namespace mismatch (`Fallout.Common.ProjectModel`) fixed to match the assembly name (`Fallout.Solutions`, plural per BCL convention to avoid `Fallout.Solution.Solution` awkwardness). - **Package IDs**: `Fallout.SolutionModel` → `Fallout.Solution`, `Fallout.VisualStudio.SolutionPersistence` → `Fallout.Persistence.Solution`. Consumers that explicitly referenced either need to update their `PackageReference`/`ProjectReference`. - **Namespaces**: `Fallout.Common.ProjectModel` → `Fallout.Solutions`; `Microsoft.VisualStudio.SolutionPersistence.{Model,Serializer,Utilities,...}` → `Fallout.Persistence.Solution.{Model,Serializer,Utilities,...}`. Replace `using` statements accordingly. The `Nuke.Common.ProjectModel.*` transition-shim path is preserved (`ShimMarker.cs` now mirrors both `Fallout.Common.*` → `Nuke.Common.*` and `Fallout.Solutions.*` → `Nuke.Common.ProjectModel.*`), so NUKE-era consumer code using `using Nuke.Common.ProjectModel; [Solution] readonly Solution Solution;` keeps compiling. @@ -123,7 +126,7 @@ The NUKE → Fallout hard fork. Originally NUKE by [@matkoch](https://github.com ### Backwards-compat: Nuke.* transition shims - **`Nuke.Common` MVP shim package** (#70): thin wrapper assembly in the `Nuke.*` namespace whose types inherit from the corresponding `Fallout.*` types — lets pre-rename consumers compile against the new packages without source changes. Lives at `src/Shims/Nuke.Common/`. - **`TransitionShimGenerator` source generator** (#69): emits shim type wrappers per-target, covering the Nuke.Common "Easy tier" surface (plain types, interfaces, enums) and static-class method delegation. Hard-tier types (sealed structs, delegates, etc.) raise `SHIM001` warnings with a pointer to `fallout-migrate`. -- **Shim packages publish to GitHub Packages** (#47): `Nuke.*` package IDs belong to matkoch on nuget.org, so the transition shim feed lives at GH Packages (`https://nuget.pkg.github.com/ChrisonSimtian/index.json`). Production-build feed is nuget.org with `Fallout.*` only. +- **Shim packages publish to GitHub Packages** (#47): `Nuke.*` package IDs belong to matkoch on nuget.org, so the transition shim feed lives at GH Packages (`https://nuget.pkg.github.com/Fallout-build/index.json`). Production-build feed is nuget.org with `Fallout.*` only. ### Vendored fork of `Microsoft.VisualStudio.SolutionPersistence` - **Replaced `matkoch.Microsoft.VisualStudio.SolutionPersistence` with a vendored fork** (#86). The upstream Microsoft package only ships `net472` + `net8.0`; our source generators need `netstandard2.0`. Matt's fork had the netstandard2.0 patches — we forked his repo at [`ChrisonSimtian/vs-solutionpersistence`](https://github.com/ChrisonSimtian/vs-solutionpersistence) (preserves the MIT license + upstream Microsoft history + attribution chain). Sources are a submodule at `vendor/vs-solutionpersistence/`; wrapper csproj at `src/Fallout.VisualStudio.SolutionPersistence/` compiles them with the TFMs we need. Assembly name stays `Microsoft.VisualStudio.SolutionPersistence` so type identity is preserved. (Note: in 10.2 the wrapper packed as `IsPackable=false` and caused the restore bug fixed in 10.3.0 above — known issue, fix-forward.) diff --git a/build/Build.CI.GitHubActions.cs b/build/Build.CI.GitHubActions.cs index 835e50ea9..6a272c419 100644 --- a/build/Build.CI.GitHubActions.cs +++ b/build/Build.CI.GitHubActions.cs @@ -1,37 +1,51 @@ using Fallout.Common.CI.GitHubActions; using Fallout.Components; -// macOS and Windows runs are reserved for post-merge validation on the -// long-lived branches (experimental, main, release/YYYY, support/*). PRs and -// feature-branch pushes get Linux-only for fast, cheap feedback. Cross-platform -// regressions on those branches surface as a red commit — same fail-fast model. +// Cross-platform (macOS/Windows) full Test+Pack is gated to RELEASE INTENT +// (#318/#326): it runs only on a PR into a production branch (release/YYYY, +// support/*) and on a release tag push (v*) — never on routine pushes to +// main/experimental, never on a per-merge basis. On main/experimental "we've +// got our edge": the ubuntu-latest PR gate + the alpha/preview pipelines. +// (workflow_dispatch as a manual cross-platform trigger isn't emitted here — +// the generator only writes workflow_dispatch when it has inputs; GitHub's +// built-in run re-run covers the on-demand case.) +// +// concurrency cancel-in-progress (#322): superseded runs are cancelled rather +// than stacked. Never applied to release.yml (a publish must not be cancelled). [GitHubActions( "macos-latest", GitHubActionsImage.MacOsLatest, FetchDepth = 0, - Submodules = GitHubActionsSubmodules.Recursive, - OnPushBranches = new[] { ExperimentalBranch, MainBranch, ReleaseBranchPattern, SupportBranchPattern }, + ConcurrencyGroup = "${{ github.workflow }}-${{ github.ref }}", + ConcurrencyCancelInProgress = true, + OnPushTags = new[] { "v*" }, + OnPullRequestBranches = new[] { ReleaseBranchPattern, SupportBranchPattern }, + OnPullRequestExcludePaths = new[] { "docs/**", ".assets/**", "**/*.md" }, InvokedTargets = new[] { nameof(ITest.Test), nameof(IPack.Pack) }, PublishArtifacts = false)] [GitHubActions( "windows-latest", GitHubActionsImage.WindowsLatest, FetchDepth = 0, - Submodules = GitHubActionsSubmodules.Recursive, - OnPushBranches = new[] { ExperimentalBranch, MainBranch, ReleaseBranchPattern, SupportBranchPattern }, + ConcurrencyGroup = "${{ github.workflow }}-${{ github.ref }}", + ConcurrencyCancelInProgress = true, + OnPushTags = new[] { "v*" }, + OnPullRequestBranches = new[] { ReleaseBranchPattern, SupportBranchPattern }, + OnPullRequestExcludePaths = new[] { "docs/**", ".assets/**", "**/*.md" }, InvokedTargets = new[] { nameof(ITest.Test), nameof(IPack.Pack) }, PublishArtifacts = false)] -// pull_request only — same-repo branches would otherwise fire both push and -// pull_request events on every push, double-running the validation. -// -// CheckoutRef = github.head_ref pins checkout to the PR source branch instead of the merge SHA, -// keeping HEAD attached so GitHubTasksTest.GitHubRepositoryFromLocalDirectoryTest (which reads -// .git/HEAD via GitRepository.FromLocalDirectory) resolves a non-null branch. +// The Linux PR gate — the only required status check. pull_request only: +// feature-branch pushes run zero CI until a PR is opened against a long-lived +// branch (#327). CheckoutRef = github.head_ref pins checkout to the PR source +// branch instead of the merge SHA, keeping HEAD attached so +// GitHubTasksTest.GitHubRepositoryFromLocalDirectoryTest (which reads .git/HEAD +// via GitRepository.FromLocalDirectory) resolves a non-null branch. [GitHubActions( "ubuntu-latest", GitHubActionsImage.UbuntuLatest, FetchDepth = 0, - Submodules = GitHubActionsSubmodules.Recursive, + ConcurrencyGroup = "${{ github.workflow }}-${{ github.ref }}", + ConcurrencyCancelInProgress = true, CheckoutRef = "${{ github.head_ref }}", // Trigger for PRs targeting experimental, main, or any release/YYYY / support/* // branch — all are long-lived and protected; all require the ubuntu-latest check. diff --git a/build/Build.cs b/build/Build.cs index 823c3535f..b1ecb3288 100644 --- a/build/Build.cs +++ b/build/Build.cs @@ -127,31 +127,41 @@ from framework in project.GetTargetFrameworks() [Parameter] [Secret] readonly string NuGetApiKey; - // Publishing to nuget.org now that the Fallout.* rename has landed (#54). - // Requires NUGET_API_KEY in repo secrets — see release.yml. - string IPublish.NuGetSource => "https://api.nuget.org/v3/index.json"; - string IPublish.NuGetApiKey => NuGetApiKey; - + // Two publish channels (FALLOUT001 — see IPublish.PublishTargets). Routing replaces the + // old single-feed push + the hand-rolled `dotnet nuget push` in the workflows (#333): + // - github-packages: EVERY package, incl. the Nuke.* shims — that ID is owned by the + // original NUKE maintainer (#47), so the shims only ever go here. Keyed by the GitHub token. + // - nuget.org: Fallout.* ONLY, never the Nuke.* shims. Keyed by NUGET_API_KEY. + // Select per run from CI with `dotnet fallout Publish --publish-to `. PublishTarget.SkipDuplicate + // (default true) keeps re-runs idempotent if a version already exists on a feed. +#pragma warning disable FALLOUT001 // opting our own build into the experimental multi-channel publish surface + IEnumerable IPublish.PublishTargets => new[] + { + new PublishTarget + { + Name = "github-packages", + Source = "https://nuget.pkg.github.com/Fallout-build/index.json", + ApiKey = From().GitHubToken, + }, + new PublishTarget + { + Name = "nuget.org", + Source = "https://api.nuget.org/v3/index.json", + ApiKey = NuGetApiKey, + IncludePackages = new[] { "Fallout.*" }, + ExcludePackages = new[] { "Nuke.*" }, + }, + }; +#pragma warning restore FALLOUT001 + + // The workflows now gate which channel publishes (via --publish-to); no on-branch + // requirement here. Missing keys fail per-target inside Publish, so a stray local + // `dotnet fallout Publish` is safe (no key → clear error, no push). Target IPublish.Publish => _ => _ .Inherit() .Consumes(From().Pack) - .Requires(() => GitRepository.IsOnMainBranch() && Host is GitHubActions && GitHubActions.Workflow == ReleaseWorkflow) .WhenSkipped(DependencyBehavior.Execute); - // Filter `Nuke.*` shim packages out of the nuget.org push — that ID is owned by - // the original NUKE maintainer. The shims still build and pack as artifacts; they - // are pushed to GitHub Packages by a follow-up step in .github/workflows/release.yml - // (#47). - IEnumerable IPublish.PushPackageFiles - => From().PackagesDirectory.GlobFiles("*.nupkg") - .Where(x => !x.NameWithoutExtension.StartsWith("Nuke.", StringComparison.OrdinalIgnoreCase)); - - // `--skip-duplicate` makes nuget push idempotent: if a version is already on the feed, - // skip it instead of erroring. Lets us rerun release.yml safely when a single package - // fails mid-batch without nuking the whole pipeline on retry. - Configure IPublish.PushSettings => _ => _ - .EnableSkipDuplicate(); - IEnumerable NuGetPackageFiles => From().PackagesDirectory.GlobFiles("*.nupkg"); diff --git a/docs/agents/conventions.md b/docs/agents/conventions.md index b85d0e49f..555ce8125 100644 --- a/docs/agents/conventions.md +++ b/docs/agents/conventions.md @@ -34,9 +34,25 @@ public sealed class NewPluginHost - **Channel discipline differs.** On the `experimental` (alpha) / `main` (preview) test lanes, 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. - **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. +## CI pipeline & triggers + +Shaped by [milestone #18](https://github.com/ChrisonSimtian/Fallout/milestone/18) and the [ADR-0004](../adr/0004-calendar-versioning-and-dual-pace-channels.md) ladder. Invariants: + +- **Feature branches run zero CI until a PR is opened.** Push triggers list **only** long-lived branches; nothing fires on `feature/*`, `bugfix/*`, etc. until they're PR'd against `experimental`/`main`/`release/*`/`support/*`. Do **not** add a working-branch pattern to any `OnPush*`/`branches:` trigger. +- **The Linux PR gate (`ubuntu-latest`) is the only required check** — runs on PRs to the four long-lived branches. +- **`experimental` (push) → `-alpha`, `main` (push) → `-preview`** to GitHub Packages (`experimental.yml` / `preview.yml`). +- **Cross-platform `windows`/`macos` are gated to release intent** — PR-to-`release/*`/`support/*` or a `v*` tag push only. They do **not** run on `main`/`experimental` pushes. ("On `main` we've got our edge.") +- **`concurrency: cancel-in-progress` on every build workflow except `release.yml`** — never cancel a publish mid-flight. +- **Canonical CI-ignore paths:** `docs/**`, `.assets/**`, `**/*.md` — applied to every PR/push trigger. +- The `ubuntu-latest` / `windows-latest` / `macos-latest` workflows are **generated** from `build/Build.CI.GitHubActions.cs` — edit the attributes + constants there and regenerate (`./build.sh`), never hand-edit the `.yml`. `experimental.yml` / `preview.yml` / `release.yml` are hand-written. +- **Every publishing lane runs `Test` before it publishes** (#324). `experimental.yml`, `preview.yml`, and `release.yml` all run a single `dotnet fallout Test Pack` invocation — NUKE executes it as discrete internal stages (Restore → Compile → Test → Pack) and fails at the breaking stage, so a test failure stops the job before the push step. Don't split a lane into separate `dotnet fallout Compile`/`Test`/`Pack` steps — each invocation re-runs the dependency graph (double-compile); the single invocation *is* the staged build. +- **Caching** (#328): every workflow caches `~/.nuget/packages` + `.fallout/temp`, keyed on `global.json` + `**/*.csproj` + `Directory.Packages.props` (the dependency-affecting set), with a `restore-keys:` prefix fallback for partial restores. There is no `packages.lock.json` to add to the key, and build outputs (`bin`/`obj`) are deliberately **not** cached (stale-artifact correctness risk). + ## What not to do - Don't reintroduce `source/` — production code lives under `src/`, tests under `tests/`. Same for `images/` (now `.assets/`). +- Don't add `main`/`experimental` (or any working-branch pattern) to the **push** triggers of the cross-platform workflows — they're release-intent-gated on purpose (milestone #18 / #318 / #326). +- Don't add `submodules: recursive` to checkouts — there are no submodules (no `.gitmodules`); it's a dead init step. - Don't add `secret`/`default` defaults to tool JSON files (see CONTRIBUTING.md). - Don't introduce a new test framework or assertion library — stay on xUnit + FluentAssertions + Verify. - Don't commit `output/` or any `bin/`/`obj/` directory. diff --git a/docs/agents/release-and-versioning.md b/docs/agents/release-and-versioning.md index 64493bf57..b6ab8dec4 100644 --- a/docs/agents/release-and-versioning.md +++ b/docs/agents/release-and-versioning.md @@ -92,7 +92,7 @@ If you only discover the breaking nature mid-review, apply all relevant steps be | Job | Environment | Fires on tag push? | What ships | Gating | |---|---|---|---|---| | `publish-nuget-org` | `nuget-org` | **No — opt-in only** via `workflow_dispatch` flag | `Fallout.*.nupkg` to https://api.nuget.org/v3/index.json | Workflow flag + approval-gated env | -| `publish-github-packages` | `github-packages` | Yes | **All** `*.nupkg` (Fallout.* + Nuke.*) to https://nuget.pkg.github.com/ChrisonSimtian/index.json | None | +| `publish-github-packages` | `github-packages` | Yes | **All** `*.nupkg` (Fallout.* + Nuke.*) to https://nuget.pkg.github.com/Fallout-build/index.json | None | | `publish-github-releases` | `github-releases` | Yes | All `*.nupkg` attached to a GitHub Release on the tag, auto-generated notes | None | ### Test lanes (from `experimental` and `main`) diff --git a/docs/experimental-apis.md b/docs/experimental-apis.md index e51405716..3bf307a2d 100644 --- a/docs/experimental-apis.md +++ b/docs/experimental-apis.md @@ -48,7 +48,7 @@ Status values: **Experimental** (live, opt-in), **Promoted** (attribute removed, | ID | Surface | Introduced | Status | Notes | |----|---------|------------|--------|-------| -| _none allocated yet_ | — | — | — | First experimental API to land claims `FALLOUT001`. | +| `FALLOUT001` | `Fallout.Components.IPublish.PublishTargets` / `.PublishTo` (multi-channel publishing) | 2026.1 | Experimental | Fan a single `Pack` output across multiple feeds with per-feed package-ID routing (`PublishTarget`). Shape may change while the CD model firms up (epic #332). Promote by deleting the attribute once `ReleaseChannel`/`DeploymentTarget` (#334) settle. |