Skip to content

refactor(arch)!: onion layering + namespace realignment (ADR-0006)#376

Open
ChrisonSimtian wants to merge 46 commits into
experimentalfrom
refactor/onion-architecture
Open

refactor(arch)!: onion layering + namespace realignment (ADR-0006)#376
ChrisonSimtian wants to merge 46 commits into
experimentalfrom
refactor/onion-architecture

Conversation

@ChrisonSimtian

Copy link
Copy Markdown
Collaborator

Supersedes #359 — that PR was auto-closed when its branch spike/shim-migration-redesign was renamed to refactor/onion-architecture (the name now reflects what it is). Same branch, same commits (9e8712b9), same work.

Collapses the 5 stacked spike PRs (#343, #349, #350, #351) into one. The onion realignment is one piece of work, so it reviews as one PR. Commit history is kept (ring by ring).

What

Re-layer the codebase into onion rings, with namespace = project = assembly = package = ring.

Rings:

  • Fallout.Domain — target graph, planning, execution status. No Fallout deps.
  • Fallout.ApplicationFalloutBuild, Target, [Parameter], the engine, ports, tool vocabulary, Components.
  • Fallout.Infrastructure.* — adapters behind ports: process/tool runner, CI hosts, project/solution readers.
  • Fallout.Kernel* — shared kernel: helpers + fluent IO (AbsolutePath, glob, HTTP, compression).
  • Fallout.Cli — composition root.

Key changes:

  • Dissolve Fallout.Common (the catch-all five projects shared). Types move to the ring they belong to.
  • Rename projects/assemblies/packages to match the ring (e.g. Fallout.BuildFallout.Application, Fallout.Utilities*Fallout.Kernel*, Fallout.ProjectModelFallout.Infrastructure.ProjectModel).
  • Split mixed projects into ring-pure halves (Tooling → Application + Infrastructure; Solution → Application + Infrastructure).
  • Add a thin Fallout meta-package as the single entry point (replaces Fallout.Common).
  • One architecture fitness test per ring enforces the dependency rule.
  • Drop net472 (Fallout is dotnet build only).
  • Rebuild the Nuke.* shim + fallout-migrate on one canonical Nuke↔Fallout map. 3 shim packages → 2 (Nuke.Build deleted). Drop Cake migration.
  • Realign test projects to the rings (renames + splits).

Why

The structure was inherited 1:1 from NUKE. Namespaces did not match projects, and Fallout.Common was a horizontal catch-all that hid the real layering. This makes the layers explicit and enforced by tests. See ADR-0006.

Breaking change

Yes. Public namespaces, assembly names, and package IDs change. Native Fallout.* code must update its usings and package refs.

Tests

463 pass, 0 fail, 7 skip. Local Compile + Test green on Windows / .NET 10.

Replaces

#343, #349, #350, #351, #359 (closed in favour of this PR).

ChrisonSimtian and others added 30 commits June 3, 2026 14:51
Records the decision to realign the wild NUKE-inherited structure to explicit
onion layers (Fallout.Domain / .Application / .Infrastructure / .Cli root),
namespace = project = layer, dissolving the Fallout.Common catch-all. Breaking,
on experimental, batched to 2027.0.0 with re-pointed Nuke.* shims; amends the
rebrand-plan's deferral of this work. Spike 0002 proves the mechanics on the
innermost (Domain) ring first.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…strategy

- Sharpen why Fallout.Components stays an outer recipes layer: it depends on
  the tool wrappers (ICompile calls DotNetBuild), so Components -> Infrastructure;
  folding it into Application would invert the onion. Folding is possible only by
  first porting tool execution behind an Application-owned port (a separate step).
- Defer the migration/shim strategy wholesale: don't re-point the shim ring-by-ring
  toward a moving target. Redesign it as its own phase/ADR once the shape settles;
  Nuke.* parity may lapse on experimental during the work. Spike 0002 simplified
  to move + reference-fixup + fitness (no shim re-point).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…is the port

Reviewing DotNetBuild's shape showed the tool wrappers are ~pure command
builders (DotNetBuildSettings : ToolOptions is data; only ProcessTasks.StartProcess
touches the OS, statically). So the real infrastructure seam is process/tool
EXECUTION, not the wrappers, and there's no useful 'abstract build' port (ICompile
is irreducibly DotNet-specific).

Corrected layer map: tool vocabulary + Components live in Application; Infrastructure
is the I/O adapters behind ports; a process/tool-execution port is the seam. Resolves
Components into Application instead of an outer ring, and makes builds testable. Adds
an explicit ring-by-ring Sequence with the tooling/execution-port spike as step 3.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
ADR-0006 step 1. Rename Fallout.Core -> Fallout.Domain (project + test project)
and move its two mis-namespaced execution types (ITargetModel, ExecutionStatus)
out of the Fallout.Common.* catch-all into Fallout.Domain.Execution; Planning ->
Fallout.Domain.Planning. Consumers in Fallout.Build gain a using; the intra-repo
ExecutionStatus type-forwarder is re-pointed.

The innermost-ring fitness test now asserts Domain depends on NO outer ring,
including Fallout.Common (the realignment goal). Full solution builds and all
tests pass (incl. Nuke.* shim tests); the solution-generator Verify snapshot is
updated for the project rename.

BREAKING CHANGE: ITargetModel/ExecutionStatus move from Fallout.Common.Execution
to Fallout.Domain.Execution. On experimental; shim/migration parity deferred
(ADR-0006). Batched to 2027.0.0.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…CHANGELOG

The 2026 major has not cut (no calendar GA tag; CHANGELOG [Unreleased] — 2026.0
still accumulates breaking changes), so breaking realignment work rides the 2026
cut and ships this year — not 2027 as ADR-0006 previously stated. Rings that miss
the 2026 cut roll to 2027. Adds the Domain-ring CHANGELOG entry.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Roslyn syntax tool for the ADR-0006 namespace moves: rewrites namespace decls in
the source project + qualified refs to moved types, and fixes usings by type
identity. Dry-run quantifies the Application ring: 91 moved types across ~392
files.

Finding: syntactic reference-resolution is not precise enough for a clean apply —
simple-name collisions across namespaces (e.g. lower layers like ProjectModel
matching a moved type's name) cause spurious usings and risk CS0104 ambiguities.
The namespace-decl + qualified-name passes are correct; the using fixup needs a
semantic upgrade (MSBuildWorkspace + symbol resolution) before applying. Dry-run
only; mutates nothing.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…ution)

Resolves each reference by the symbol it binds to, so namespace moves are precise:
no spurious usings, no CS0104. Opens csprojs individually (sidesteps .slnx). Drops
only usings orphaned by the move (leaves pre-existing unused usings alone). Dry-run
on the Application ring: 91 moved types, 238 files, 12 qualified refs.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…onion step 2)

ADR-0006 step 2, via the semantic OnionRewriter (MSBuildWorkspace + symbol
resolution). Every type Fallout.Build declared under Fallout.Common.* (FalloutBuild,
Target, [Parameter], Host, the ports, the execution engine, value injection) moves
to Fallout.Application.*; references fixed repo-wide by the symbol each binds to.
~297 files; full solution builds and tests green (~450 tests, incl. Domain fitness).

Tool refinements landed here: name-based moved-type classification (cross-project),
package-consumer skip, attribute/extension-method detection, nested-type qualified
refs, evacuated- vs surviving-namespace using reconciliation. Four dangling usings
hand-finished; Verify snapshots (solution generator, cake-migration) updated.

Deferred (ADR-0006): Nuke.* shim parity lapses — Nuke.Consumer + Nuke.Common.Shim.Tests
temporarily excluded from the solution, to be re-addressed by the redesigned migration
phase. Project-FILE rename (Fallout.Build dir/csproj -> Fallout.Application) still to do
— namespaces are migrated; the assembly is renamed in a mechanical follow-up.

BREAKING CHANGE: Fallout.Common.* core-API namespaces -> Fallout.Application.*. On
experimental; batched to the 2026 major.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…n step 3

ADR-0006 step 3 (first piece of the Infrastructure ring). The single impure step of
running a tool — spawning an OS process — now goes through an injectable IProcessRunner
port (default SystemProcessRunner) instead of a static Process.Start. This lets the
tooling vocabulary stay pure (prerequisite for moving the wrappers to Application in
step 4) and makes builds unit-testable by swapping the runner. Non-breaking; behaviour
identical — Tooling builds, 64/64 tests pass.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
… lessons)

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…tep 4a)

Move the build-composition interface family (ICompile, IRestore, IPack,
IPublish, ITest, IHas*, Configuration, …) from Fallout.Components into the
Application ring: Fallout.Components → Fallout.Application.Components. It
composes the tool/CI vocabulary, so it belongs in Application (ADR-0006).

Native consumers rewrite `using Fallout.Components;` →
`using Fallout.Application.Components;`. The Nuke.Components transition shim
is unaffected for consumers — its public Nuke.Components.* face is unchanged;
ShimMarker's generator source prefix is repointed to the new namespace
(a string literal the semantic rewriter intentionally leaves alone). The
Fallout.Components assembly/project keeps its filename until the later
mechanical rename step.

Tooling: generalize tools/OnionRewriter to a Rule[] table (multi-assembly,
multi-prefix) so the remaining infra-ring steps just add rules. Fix a
nested-type classification bug surfaced here — a nested type's
ContainingNamespace is its enclosing namespace, so keying the moved-set by the
symbol's own name misclassified SignPathSettings as residual and added a
dangling using to the evacuated namespace; classify by the outermost
enclosing type instead, and never re-import a non-surviving namespace.

Full suite green (Components.Tests, Nuke.Components.Shim.Tests, no Verify churn).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…ture (onion step 4b)

Split the tool layer across the Application and Infrastructure rings
(ADR-0006). The tool wrappers Fallout.Common.Tools.* and the vocabulary they
build on — ToolTasks, ToolOptions, Output, Configure<T>, the IProcess/
IProcessRunner ports, requirement & attribute types, ProcessException — move
to Fallout.Application.Tools.* / Fallout.Application.Tooling. The impure
executor that shared the Fallout.Common.Tooling namespace (ProcessTasks,
SystemProcessRunner, Process2, ProcessExtensions, ToolExecutor, and the
tool/package/version resolvers) moves to Fallout.Infrastructure.Tooling.

Per the maintainer's "true homes, defer port fix" call: types land where they
belong, accepting ToolTasks(App) → ProcessTasks(Infra) and resolver I/O as a
tracked Application→Infrastructure dependency to be inverted behind ports in a
follow-up (the rings are namespaces, not yet separate assemblies, so nothing
enforces it). The Nuke.Common.Tools/Tooling shim aliases lapse, consistent
with steps 1–2 and the deferred shim redesign.

Native consumers rewrite `using Fallout.Common.Tools.*;` →
`using Fallout.Application.Tools.*;` and `using Fallout.Common.Tooling;` →
`using Fallout.Application.Tooling;` (+ Fallout.Infrastructure.Tooling where
they touch the executor). Assembly/project files unchanged; namespaces only.

Tooling: OnionRewriter gains per-type overrides (split one namespace across
rings), multi-assembly rules, lost-ancestor usings (a moved file loses
implicit access to its old parent namespace — e.g. Fallout.Common's
EnvironmentInfo/NotNull/Assert — so add an explicit using), and cref rewriting
(descend into doc-comment trivia so cref imports/FQNs follow the move).

Full suite green; 7 cake-migration Verify snapshots re-accepted (namespace-only).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…lication ring (onion 4b follow-up)

Clears the Application→Infrastructure dependency that step 4b left tracked. The
impure tool-execution services now sit behind ports in Fallout.Application.Tooling
— IProcessExecutor, IToolPathResolver, IToolVersionResolver — resolved through a
ToolingServices locator. Fallout.Infrastructure.Tooling registers thin adapters
(forwarding to ProcessTasks / the resolvers) via a module initializer, so the
Application ring reaches Infrastructure only through the ports.

All Application-ring call sites are routed through the ports: ToolTasks.Run /
.ToolPath, ToolResolver, the tool wrappers (MSpec/DotCover/Xunit/Codecov/
PowerShell/Docker), Latest{NuGet,Npm}VersionAttribute, ToolRequirement version
lookups, Configure's DefaultLogOutput, CredentialStore, ToolRequirementService,
BuildManager's resolver config-push, InvokeBuildServerConfigurationGeneration,
and FalloutBuild.

ProcessExtensions (pure IProcess/Output helpers — AssertWaitForExit, StdToJson,
…) is reclassified from Infrastructure.Tooling back to Application.Tooling, where
the vocabulary that uses it lives (a step-4b misclassification). A
ModuleInitializerAttribute polyfill covers Fallout.Tooling's netstandard2.0 TFM.

New tests/Fallout.Architecture.Tests runs a NetArchTest fitness gate asserting
Fallout.Application.* has no dependency on Fallout.Infrastructure.* — green. It
lives in its own project so loading every ring's assembly cannot perturb the
AppDomain-scan/Verify tests in Fallout.Build.Tests.

Catch-all Fallout.Common.* code outside the Application ring (CI adapters, a few
attributes, the Cli composition root, MSBuildTasks) still reaches Infrastructure;
that folds in with step 5 as those namespaces get rings.

Full suite green; solution-generator snapshot re-accepted (new project entry).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…on step 5a)

Establishes Fallout.Kernel, the innermost shared ring (ADR-0006). The shared
utility/IO layer moves from Fallout.Common.Utilities* / Fallout.Common.IO to
Fallout.Kernel / .Collections / .IO / .Net: pure helpers (collections, string/
text, reflection, crypto, JSON/YAML, guards), the AbsolutePath/RelativePath
value types AND their fluent filesystem operations, plus HTTP/compression
helpers.

Filesystem is treated as a kernel-level capability (like the BCL File/Directory):
the fluent AbsolutePath API (.ReadAllText/.GlobFiles/.CreateDirectory/…) is used
pervasively across the Application ring, so routing it through ports would be
impractical and anti-ergonomic — it lives in Kernel, not Infrastructure. Pushing
the genuinely-external adapters (HTTP/FTP, compression) further out to
Infrastructure is deferred for the same reason (same ports decision as the
filesystem API). The Application⊥Infrastructure fitness gate still passes.

Native consumers rewrite `using Fallout.Common.Utilities;` → `using Fallout.Kernel;`
(and .Collections/.Net) and `using Fallout.Common.IO;` → `using Fallout.Kernel.IO;`.
Assembly/project files unchanged; only namespaces moved (the Fallout.Utilities →
Fallout.Kernel project rename rides the deferred project-rename step). A few
root-Fallout.Common guards (Assert/NotNull/EnvironmentInfo) stay in the
dissolving catch-all for now and fold into Kernel later.

Tooling: OnionRewriter rules retargeted for 5a. A handful of files the workspace
didn't fully analyse were mopped up by hand (skipping package consumers and the
Migrate rebrand fixtures, re-adding lost-ancestor usings) — see lesson #12.

Full suite green.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…ts (onion step 5b)

Move the concrete CI host providers (AppVeyor, AzurePipelines, TeamCity,
GitHubActions, GitLab, TravisCI, Jenkins, Bitrise, Bitbucket, Bamboo,
SpaceAutomation) and their config generators from Fallout.Common.CI.* to
Fallout.Infrastructure.CI.*.

The Application ring uses provider-SPECIFIC capabilities (PublishTestResults,
PushArtifact, SetBuildNumber, UpdateBuildNumber, Token, …), so a generic host
abstraction can't capture them. Instead, per-provider ports in
Fallout.Application.CI — IAppVeyor/IAzurePipelines/ITeamCity/IGitHubActions —
plus a CiHost accessor that casts the detected Host.Instance to the port (null
when not running on that host). No registration needed: Host.Instance is the
existing detection seam, and the providers (subclasses of Host) implement the
ports. Components (ITest/IReportCoverage/ISignPackages/ICreateGitHubRelease) and
version/coverage attributes now call CiHost.X instead of X.Instance, so the
Application ring keeps no dependency on Fallout.Infrastructure.* — the fitness
gate still passes. The two enums the ports expose move to Fallout.Application.CI
as vocabulary. Nuke.Common CI host shims repointed to the new namespace.

(The generic CI host abstraction — ADR-0005 IBuildHost/IBuildReporter, #341 —
stays a separate additive effort.)

Tooling: OnionRewriter rules retargeted for 5b (CI → Infrastructure.CI, enums
overridden to Application.CI). Lesson #13: alias-qualified `global::Ns.Type`
refs aren't rewritten (Left includes `global::`) — hit in the shims, mopped up.

Full suite green; Application-ring fitness gate green.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…amespace move

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Extends the 5a "filesystem is kernel-level" ruling to the rest of the
fluent external-IO vocabulary. Investigation found every external-IO
category the original plan earmarked for Infrastructure (HTTP fluent
client, compression .ZipTo()/.UnZipTo(), HttpTasks/FtpTasks, glob) is
consumed by gated Application-ring code, so none can move cleanly and
all would otherwise need 5c-style ports. They're thin BCL-ergonomic
layers, not genuine adapters — kept in Kernel; gate stays green.

- spike 0003: mark 5d resolved-by-decision; correct the step-5 summary
- ADR-0006: add Amendment reversing the "IO/Net/compression are
  genuinely infrastructure" default for the fluent vocabulary

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
… tree)

Reshape the Solution/Project/SolutionFolder/SolutionItem model from live
wrappers over the vendored SolutionModel into a populated POCO tree with
parent links + an opaque object Handle (carries the serializer model for
Save without the inner ring naming vendored types). Public surface
preserved. Translation walker lives in ReadSolution for now (still in the
model project, still vendored-coupled — extracted to the Infrastructure
adapter in phase B).

The strongly-typed solution generator is coupled to the model ctor (it
emits Solution subclasses), so update its template in lockstep: drop the
vendored `using`, emit `(AbsolutePath, object handle)` ctor, ctor-less
folder subclass. Namespaces unchanged (Fallout.Solutions) — no ring move
yet. Build + Solution/ProjectModel/generator tests + dogfood build green.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…or ports

Invert the impure solution/project I/O behind Application-ring ports
(ADR-0006), mirroring the 4b ToolingServices pattern:

- ISolutionSerializer + IProjectEditor + SolutionServices locator added to
  the model project. The serializer adapter (vendored .sln/.slnx + the
  vendored→Fallout translation walker + opaque-handle save) is co-hosted
  with the model and self-registers via a [ModuleInitializer] (+ ns2.0
  polyfill) — co-hosted like ToolingServices so registration is guaranteed
  on assembly load.
- Model.cs + SolutionModelExtensions now route through the port and no
  longer reference any vendored type — the inner model is pure.
- ProjectEditorAdapter (Microsoft.Build) in Fallout.ProjectModel implements
  IProjectEditor (GetProperty/SetProperty/HasPackageReference) +
  module-init. HasPackageReference moves from the Infra ProjectExtensions
  to a port-backed Application extension (its only framework-ring caller is
  Components/ITest); the rest of the MSBuild ProjectExtensions stay Infra
  (consumers/tests reference them directly).
- Telemetry.CheckAwareness (dead code, telemetry disabled) rewired through
  IProjectEditor, dropping Fallout.Build's Microsoft.Build coupling.
- Drop now-dead Fallout.Build/Fallout.Common → Fallout.ProjectModel refs;
  add explicit ProjectModel refs to the Cli composition root + the dogfood
  build (consumer of the MSBuild GetTargetFrameworks extension).

Namespaces unchanged (Fallout.Solutions) — ring move is phase C. Full
build + Solution/ProjectModel/generator/gate tests green.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…namespace move)

Move the Fallout.Solutions namespace (declared across Fallout.Solution,
Fallout.ProjectModel, Fallout.Common) onto the onion rings via OnionRewriter,
splitting it three ways:
  • model + ports + [Solution] vocabulary → Fallout.Application.Solutions
    (the abstract concept, inner ring)
  • vendored .sln/.slnx serializer adapter → Fallout.Infrastructure.Solutions
  • Microsoft.Build evaluator (ProjectModelTasks/ProjectExtensions) →
    Fallout.Infrastructure.ProjectModel
Realised with per-type overrides + a multi-assembly rule (18 moved types,
~47 files). After phase B the model already routes I/O through the ports,
so the move adds no Infrastructure dependency to the Application ring.

Companion edits the semantic rewriter can't make (string literals):
  • generator template + attribute-detection string → Application.Solutions
  • ShimMarker: mirror both new namespaces → Nuke.Common.ProjectModel
  • cake ClassRewriter using-list + project template + generator test
    snippets → Application.Solutions
  • re-accepted 8 namespace-churned Verify snapshots (generator + cake)

Fitness gate now loads Fallout.Application.Solutions and stays green
(Application ⊥ Infrastructure). The strongly-typed-solution generator's
compile-time ReadSolution works unchanged — the serializer adapter is
co-hosted in the model assembly the generator already bundles, so its
[ModuleInitializer] fires. Full suite (14 projects) + dogfood ./build.sh
Compile green (runtime [Solution] injection through the port verified).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…realization

Mark 5c complete in spike 0003: Fallout-owned POCO model + opaque handle,
ISolutionSerializer/IProjectEditor ports, co-hosted adapters, the
vendored-entanglement + false-positive findings, and the pre-existing
ClassRewriter/template stale-namespace debt found but left out of scope.
All substantive ring moves are now landed.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…template

The cake migration's hard-coded using list (ClassRewriter.NamespaceImports)
and the `dotnet fallout :setup` Build.cs template still emitted pre-onion
Fallout.Common.* namespaces removed in steps 4a/4b/5b/5c — so migrated and
freshly-scaffolded builds referenced dead namespaces (CS0246).

- ClassRewriter: map dead Fallout.Common.{Execution,Tooling,Tools.GitVersion}
  → Fallout.Application.*; drop entries already contributed by the static
  imports (Fallout.Common, Kernel.IO, Tools.DotNet, Tools.SignTool); add
  .Distinct() so the emitted using list has no duplicates (the static-import
  namespaces previously double-added Kernel.IO).
- Template Build.cs: map dead Fallout.Common.{CI,Tooling,Tools.*} +
  Fallout.Solutions → their Fallout.Application.* homes. Kept the namespaces
  that genuinely still live under Fallout.Common (root, .Execution, .Git
  attribute, .ChangeLog, EnvironmentInfo) — Git/ChangeLog were only partially
  migrated by earlier steps.
- Re-accepted the 7 cake-migration Verify snapshots.

Out of scope (pre-existing, noted): the template still references the removed
[CheckBuildProjectConfigurations] attribute, and the GitRepository class vs
[GitRepository] attribute are split across Application.Git/Common.Git from an
incomplete earlier move — separate follow-ups, not namespace staleness.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Stragglers from the earlier rings: GitRepositoryAttribute was still in
Fallout.Common.Git while the GitRepository model already lived in
Fallout.Application.Git, and the changelog helpers were still in
Fallout.Common.ChangeLog. Move both into the Application ring via
OnionRewriter (pass 1):
  • Fallout.Common.Git → Fallout.Application.Git (attribute joins the model)
  • Fallout.Common.ChangeLog → Fallout.Application.ChangeLog
Update the dotnet-fallout template's two Git/ChangeLog usings to match. No
shim-marker changes — the Application ring's Nuke.* shim story is the
deferred redesign, so this just brings Git/ChangeLog in line with it. Full
suite green.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…/etc → Fallout.Kernel

Four pure-utility types in the Fallout.Utilities (Kernel) project were left
in the Fallout.Common namespace when step 5a moved everything else to
Fallout.Kernel (the lesson-#12 workspace-load gap): EnvironmentInfo (5
files), Assert, AsyncHelper, ArgumentParser. Move them to Fallout.Kernel
via OnionRewriter (pass 2, source assembly = Fallout.Utilities only so the
surviving-namespace scan still sees Fallout.Common alive in the
Fallout.Common project and keeps live `using Fallout.Common;` directives).
~180 files of reference churn (Assert/.NotNull() is pervasive).

Two rewriter blind-spots fixed by hand:
- Fallout.ProjectModel files carried `using Fallout.Common;` that only ever
  resolved because Fallout.Utilities declared those Common-namespace types;
  ProjectModel doesn't reference the Fallout.Common assembly, so the using
  dangled (CS0234) once they moved. The rewriter can't see assembly-ref
  graphs — replaced/dropped the stale usings (Kernel was already imported).
- CITest.cs gained `using Fallout.Kernel;` (for .NotNull()), colliding
  Fallout.Kernel.Assert with Xunit.Assert — aliased the bare name to Xunit's.

Companion edits: template + cake ClassRewriter EnvironmentInfo import →
Fallout.Kernel; re-accepted 7 cake snapshots. Full suite + dogfood
./build.sh Compile green.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…ale cref

Record the finished partial onion moves in spike 0003, and fix a stale
doc-comment cref (Fallout.Common.Git.GitRepository →
Fallout.Application.Git.GitRepository — the class had already moved).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
… the exposed Infra dep)

Last of the partial onion stragglers:
- Fallout.Common.Execution (CheckPathEnvironmentVariable +
  HandleSingleFileExecution build-extension attributes) → join
  Fallout.Application.Execution.
- Fallout.Common.Gitter (GitterTasks) → Fallout.Application.Tools.Gitter,
  alongside its sibling notification tools (Slack/Discord/Teams/Mastodon).

Moving the two attributes into the Application ring made the fitness gate
catch a real Application→Infrastructure dependency that was previously
invisible only because the attributes sat in Fallout.Common.Execution (the
spike flagged this: they "still reach Infrastructure"). Both called
ProcessTasks (Fallout.Infrastructure.Tooling) directly — rerouted through
the IProcessExecutor port (ToolingServices.Process), the same 4b inversion
the rest of the Application ring uses. Gate green again.

Template: Fallout.Common.Execution using → Fallout.Application.Execution
(now fully evacuated). Full suite + dogfood ./build.sh Compile green.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
… ring names

First phase of aligning project files with the (already onion-aligned)
namespaces. Pure renames of the ring-pure projects (folders + .csproj +
assembly + package IDs); no namespace edits, no file moves between projects:
  Fallout.Build           → Fallout.Application
  Fallout.Components       → Fallout.Application.Components
  Fallout.ProjectModel     → Fallout.Infrastructure.ProjectModel
  Fallout.Utilities        → Fallout.Kernel
  Fallout.Utilities.IO.Compression → Fallout.Kernel.IO.Compression
  Fallout.Utilities.IO.Globbing    → Fallout.Kernel.IO.Globbing
  Fallout.Utilities.Net    → Fallout.Kernel.Net
  Fallout.Utilities.Text.Json → Fallout.Kernel.Text.Json
  Fallout.Utilities.Text.Yaml → Fallout.Kernel.Text.Yaml

Rewired ~71 files: all ProjectReferences, fallout.slnx, the SourceGenerators
DLL-bundling paths (Fallout.Utilities.dll → Fallout.Kernel.dll), the shared
AssemblyInfo.cs InternalsVisibleTo grants (Fallout.Build→Fallout.Application,
Fallout.Utilities.IO.Globbing→Fallout.Kernel.IO.Globbing), and a hard-coded
project-name lookup + the renamed-project accessors in the generator snapshot.

Package IDs change (breaking) — rides the 2026 major. Mixed projects
(Common/Tooling/Solution) split in later phases. Full suite + dogfood green.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
ChrisonSimtian and others added 16 commits June 3, 2026 14:52
… assemblies

Split the mixed Fallout.Tooling project into:
  Fallout.Application.Tooling   — tool vocabulary + ports (ToolingServices,
                                  ToolTasks, ToolOptions, ToolResolver, IProcess*)
                                  netstandard2.0;net10.0 (consumed by the
                                  Fallout.Tooling.Generator Roslyn generator)
  Fallout.Infrastructure.Tooling — executors + resolvers (ProcessTasks,
                                  ToolExecutor, *Resolver, the module-init
                                  registration) — net10.0
Rewired all consumers (App ring → Application.Tooling; CI/MSBuildTasks/Cli →
Infrastructure.Tooling), the slnx, the AssemblyInfo IVT grants, removed 6
stale `using Fallout.Infrastructure.Tooling;` directives from Application-ring
files (no real Infra dependency — confirmed by the gate), and dropped net472
from MSBuildTasks (it now references the net10.0-only Infra.Tooling).

CRITICAL FIX (BuildManager.Initialize): the assembly force-loader used
Assembly.Load, which loads metadata but does NOT run a [ModuleInitializer]
(that fires lazily on first type use). Pre-split this was masked — build code
uses ToolingServices, co-hosted with its registration. Post-split the
registration moved to Fallout.Infrastructure.Tooling, whose types nothing
references directly, so it never ran → ToolingServices.* stayed null →
swallowed NRE at build-finish (CI-config drift check). Now the force-loader
calls RuntimeHelpers.RunModuleConstructor on each Fallout.* assembly, making
split-adapter registration deterministic (also covers the Solution/ProjectModel
splits to come). Caught via dogfood ./build.sh + a stash A/B against phase 1.

Full suite + gate + dogfood (zero NREs) green.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…e assemblies

Split src/Persistence/Fallout.Solution into:
  Fallout.Application.Solutions   — Solution/Project model + ports
                                    (SolutionServices, ReadSolution) — ns2.0;net10.0
  Fallout.Infrastructure.Solutions — the .sln/.slnx serializer adapter +
                                    registration — ns2.0;net10.0
Both at src/ top level; the vendored Fallout.Persistence.Solution stays.

Source generator: reads solutions at compile time, but a Roslyn host can't
run the adapter's module initializer to populate the SolutionServices
locator. Added Fallout.Infrastructure.Solutions.SolutionReader (a public
direct entry into the adapter) and pointed the generator at it — bypassing
the runtime locator (the generator is build-time tooling, not a ring).

Wiring: rewired all consumers (model → Application.Solutions); Cli + _build
(composition roots) reference Infrastructure.Solutions so its registration is
force-loaded for runtime [Solution] injection; moved the vendored
InternalsVisibleTo + the shared AssemblyInfo IVT grants to the split names;
updated the SourceGenerators DLL-bundling. Test hosts (Solution.Tests,
ProjectModel.Tests) get a [ModuleInitializer] that RunModuleConstructor's the
adapter assembly (same reason as the generator — no BuildManager force-load
in a test host).

Full suite + gate + dogfood (zero NREs, [Solution] injection works) green.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…re assemblies + Fallout meta

The 249-file Fallout.Common catch-all is gone. Its files fan out by ring:
  Tools/* + Gitter/        → Fallout.Application.Tools   (new; tool wrappers)
  CI/* (providers)         → Fallout.Infrastructure.CI   (new; Host-detected adapters)
  CI/CiHostPorts + 2 enums → Fallout.Application (Application.CI)
  ChangeLog, Git/Execution/Solution/Tooling attrs, HttpTasks, FtpTasks,
    TemplateUtility, globbing attrs → Fallout.Application
  GitHub-coupled bits (LatestGitHubRelease, ChangeLogTasks) → Fallout.Application.Tools
  TextTasks, XmlTasks      → Fallout.Kernel (genuinely pure)

New consumer-facing meta-package **Fallout** (no code): references every ring
+ carries the MSBuild integration (Fallout.props/.targets, renamed from
Fallout.Common.*), the MSBuildTasks publish output, and the source-generator
analyzer — the packaging-anchor role Fallout.Common held. Cli, the Nuke.Common
shim, and consumers now reference Fallout; ring-internal consumers reference
the specific ring projects.

Placement was dependency-driven, not namespace-driven: injection/extension
attributes (SolutionAttribute, [Latest*], [GitRepository], globbing attrs) and
ControlFlow/Configure/Serilog-coupled IO (FtpTasks, HttpTasks, TemplateUtility)
must live in Fallout.Application despite their Kernel.IO/Application.Tooling/etc.
namespaces, because their base classes live there and the leaf ring projects
are referenced BY Fallout.Application (placing them in the leaves would cycle).
Residual noted: a handful of Kernel.IO-namespace files sit in the Application
assembly (FtpTasks/HttpTasks/globbing attrs) — a namespace-vs-assembly cleanup
for later, ring-safe (no Infra dependency, gate green).

Wired all refs + slnx (+Fallout +Application.Tools +Infrastructure.CI),
AssemblyInfo IVT grants, _build props/targets import → Fallout.props/.targets,
and _build's direct refs. Full suite + gate + dogfood (zero NREs) green.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…lout meta

- Fallout.Domain: netstandard2.1;net10.0 → net10.0 (both consumers are
  net10.0; nothing constrains it).
- Fallout.Infrastructure.ProjectModel: KEEP net8.0;net9.0;net10.0 — the
  multi-targeting is load-bearing (Directory.Packages.props pins a matching
  Microsoft.Build per TFM: net10→18.0.2, net9→17.14, net8→17.11; the standard
  MSBuildLocator SDK-matching pattern). The plan's "nothing constrains it"
  premise was wrong; reverted that part.
- Consumer wiring for the dissolved Fallout.Common package: the dotnet-fallout
  template now adds the `Fallout` meta-package; Constants.FalloutCommonPackageId
  → FalloutPackageId = "Fallout"; `:setup` version lookup/prompts and
  `:update`'s package-bump now target the meta. (The Fallout.Common NAMESPACE
  survives in Fallout.Build.Shared — Constants/LegacyEnvironment — so the using
  directives stay valid.)
- Fallout.Build.Shared: kept as the shared-primitives leaf (its residual
  Fallout.Common-root namespace is a separate namespace cleanup, not a project
  rename). Fitness gate green (now operating on ring-pure assemblies).

Full suite + gate + dogfood green.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…ADR-0006)

The structural realignment is complete — ring = project = namespace =
assembly = package. Document the project-file renames/splits, the Fallout
meta-package as the consumer anchor, the TFM decisions, and the
Assembly.Load-vs-ModuleInitializer fix (RunModuleConstructor) as the general
pattern for ring splits. Only the Nuke.* shim/Migrate redesign remains.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
… → Fallout.Application.IO

Namespace-vs-assembly tidy-up from the project-rename phase. FtpTasks,
HttpTasks, and the globbing injection attributes (FileSystemGlobbingAttributeBase
+ FileGlobbingAttribute, GlobbingOptionsAttribute) carried a Fallout.Kernel.IO
namespace but physically live in the Fallout.Application assembly (they depend
on the Application ring — ControlFlow/Configure — so they can't be Kernel).
Retargeted their namespace to Fallout.Application.IO so namespace matches
assembly/ring. Source assembly = Fallout.Application only; the 20 genuine
Kernel.IO types in Fallout.Kernel are untouched (Fallout.Kernel.IO survives,
so live `using Fallout.Kernel.IO;` directives are preserved). Consumers gain
`using Fallout.Application.IO;` where they used these 4 types. Full suite +
gate + dogfood green.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…pace → Fallout.Build.Shared

The final Fallout.Common namespace (Constants + LegacyEnvironment, physically
in the Fallout.Build.Shared assembly) is relabelled to Fallout.Build.Shared so
namespace matches assembly — the dissolved Fallout.Common catch-all is now gone
from the entire shipped codebase. Pure namespace relabel + using rewrites across
~87 files (no assembly-graph change; Constants stays in Build.Shared). Deleted a
vestigial empty Fallout.Common.FalloutBuild stub in SourceGenerators first, so
the namespace was fully evacuated and 75 dead `using Fallout.Common;` directives
dropped cleanly. Also removed the now-dead `using Fallout.Common;` from the
dotnet-fallout starter template (embedded resource the semantic rewriter can't
reach — would have been CS0246 in a freshly-scaffolded build).

(Migrate test fixtures still contain "Fallout.Common"/"Nuke.Common" as
Nuke→Fallout migration test DATA — intentionally unchanged.)

Full suite (14 projects) + gate + dogfood green.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…y gone

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…ource)

Single source of truth for the two halves of the NUKE-compat story, which the
onion realignment desynchronised. src/Shared/NukeNamespaceMap.cs lists every
(NukePrefix, FalloutPrefix, ShimPackage) row + an IsMigrationTarget flag for the
namespaces that split across rings post-onion (CI, ProjectModel, IO). Linked as
source into the three consumers (Fallout.SourceGenerators, Fallout.Migrate,
Fallout.Migrate.Analyzers) so the shim generator (Fallout->Nuke) and the
migration rewriters (Nuke->Fallout) derive from the same table.

No behaviour change yet — wired into the generator + rewriters in the next
steps. Build green.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…post-onion correct)

The onion realignment broke the migration: CodeRewriter's blind `Nuke.`→`Fallout.`
swap produced dead `Fallout.Common.*` namespaces (now `Fallout.Application.*` etc.),
and CsprojRewriter mapped the `Nuke.Common` package to the dissolved `Fallout.Common`
package. Rebuild both (and the analyzer codefix) on the shared NukeNamespaceMap:

- CodeRewriter: ordered longest-prefix-first namespace rewrite over the map
  (Nuke.Common.Tools → Fallout.Application.Tools, Nuke.Common.IO → Fallout.Kernel.IO,
  Nuke.Common → Fallout.Application, …).
- CsprojRewriter: explicit package-ID map (Nuke.Common → the `Fallout` meta-package,
  Nuke.Components → Fallout.Application.Components); unknown Nuke.* packages left
  untouched rather than rewritten to a dead Fallout.* id.
- NukeMigrationCodeFix: rewrite the whole qualified name via the map (VisitQualifiedName)
  instead of the bare `Nuke`→`Fallout` token swap.
- Added PackageIdMap to the shared map.
- Updated the migration test fixtures (CodeRewriter/Csproj/Integration + analyzer
  codefix + RealWorldSmoke) that had encoded the pre-onion broken output to the
  onion-correct Fallout.Application.*/Kernel.*/Application.Components/Fallout-meta output.

Both migration suites green (Migrate 22, Analyzers 14).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Remove the Cake→Fallout converter entirely (low demand; legacy from the
original author — Nuke migration is the realistic path). Deleted:
- src/Fallout.Cli/Rewriting/Cake/ (14 rewriter files)
- src/Fallout.Cli/Program.Cake.cs (the :cakeconvert / :cakeclean commands;
  command dispatch is reflection-based, so they de-register automatically)
- tests/Fallout.Cli.Tests/CakeConversionTests.cs + cake-scripts/ (14 fixtures)
  + the csproj items that included them
- Telemetry.Events.ConvertCake()

grep confirms zero remaining Cake references. Build + Cli tests green.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…2 shim packages

The TransitionShimGenerator no longer reads per-assembly
[assembly: ShimAllPublicTypesUnder(...)] markers — it derives its work from the
shared NukeNamespaceMap, emitting the rows whose ShimPackage equals the
compiling assembly's name. So a single Nuke.Common shim (referencing the
Fallout meta) re-exports every Nuke.Common.* sub-namespace wherever the source
types now live across the rings; the shim-package count tracks NUKE's real
consumer packages, not Fallout's assembly count.

- Removed the ShimAllPublicTypesUnderAttribute (PostInit source + ExtractMarkers)
  and the three ShimMarker.cs files.
- Deleted the Nuke.Build shim entirely (never a real NUKE consumer package;
  NukeBuild lives in Nuke.Common) + its slnx entry.
- Generator gains longest-FromPrefix-wins ownership (ShimMarker.Owns): a type is
  emitted under the most specific matching map prefix only — fixes a duplicate
  hint-name crash where Nuke.Common.Utilities→Fallout.Kernel swallowed
  Fallout.Kernel.IO types also claimed by Nuke.Common.IO.
- Kept all emission edge-cases (ambiguity skip, hand-bridge skip, static-class
  delegation, ctor mirroring, SHIM001/002) + the hand-written shims (NukeBuild
  bridge, CI Instance accessors → Infrastructure.CI, DelegateDisposable → Kernel,
  IHaz* aliases).
- Reworked TransitionShimGeneratorTest to the map mechanism (compile "as"
  Nuke.Common, fake referenced Fallout.Application assembly); re-accepted its
  snapshots + the StronglyTypedSolution snapshot (Nuke.Build accessor gone).

Full suite + gate green.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…esign)

Bring Nuke.Common.Shim.Tests + Nuke.Consumer back into the solution (excluded
during the onion realignment while the shims lapsed). They are the proof that a
NUKE-era consumer's surface — using Nuke.Common; [Parameter]/[Secret]/[Solution]/
[GitRepository]; Nuke.Common.ProjectModel.Solution; Nuke.Common.Git.GitRepository;
CI host Instance accessors; DelegateDisposable — resolves through the 2
consolidated shims.

- SampleConsumerBuild is now a pure NUKE surface (dropped the canonical Fallout.*
  imports + fully-qualified types that were a workaround for the missing shims;
  the shims provide them now). Solution/GitRepository come from the Nuke.* shim.
- Nuke.Consumer: dropped the deleted Nuke.Build ref; fixed the Target delegate
  alias (Fallout.Common.Target → Fallout.Application.Target).
- Re-accepted the StronglyTypedSolution snapshot (the two re-added projects'
  accessors).

Full suite (incl. both shim compat tests) + gate + dogfood ./build.sh Test green.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
CHANGELOG + spike 0003 + ADR-0006 amendment: one canonical Nuke<->Fallout map
drives shims (Fallout->Nuke) and migration (Nuke->Fallout); shim packages 3->2
(Nuke.Build deleted); Cake migration dropped. The realignment is structurally
and compat-complete.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Leftover from the onion namespace rewrite. Clears CS0105 in Program.Complete, Program.Secrets, and ContextAwareTask.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Test projects still used pre-onion names. Match them to the rings (project = namespace = ring).

Renames:
- Build.Tests -> Application.Tests
- Components.Tests -> Application.Components.Tests
- ProjectModel.Tests -> Infrastructure.ProjectModel.Tests
- Utilities.Tests -> Kernel.Tests

Splits (mirror the src split):
- Common.Tests -> Application.Tools.Tests + Infrastructure.CI.Tests
- Tooling.Tests -> Application.Tooling.Tests + Infrastructure.Tooling.Tests

Solution.Tests -> Infrastructure.Solutions.Tests (rename, not split: one integration test, nothing infra-only to separate).

Also updated to match: InternalsVisibleTo, test namespaces, fallout.slnx, hardcoded test paths, and the solution Verify snapshot. 463 tests pass, 0 fail.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@ChrisonSimtian ChrisonSimtian requested a review from a team as a code owner June 8, 2026 00:53
@ChrisonSimtian ChrisonSimtian added breaking-change Change is breaking — requires major version bump per CLAUDE.md semver policy. target/2026 Targets the 2026 calendar-version line (current). See ADR-0004. labels Jun 8, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

breaking-change Change is breaking — requires major version bump per CLAUDE.md semver policy. target/2026 Targets the 2026 calendar-version line (current). See ADR-0004.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant