From 3e2d232307eda71ae22f2d10b8364693cc25f368 Mon Sep 17 00:00:00 2001 From: Chrison Simtian Date: Sun, 31 May 2026 19:54:54 +1200 Subject: [PATCH 1/6] docs: ADR-0006 + spike 0002 for onion layering & namespace realignment 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 --- ...nion-layering-and-namespace-realignment.md | 76 +++++++++++++++++++ docs/adr/README.md | 1 + docs/spikes/0002-onion-domain-ring.md | 52 +++++++++++++ 3 files changed, 129 insertions(+) create mode 100644 docs/adr/0006-onion-layering-and-namespace-realignment.md create mode 100644 docs/spikes/0002-onion-domain-ring.md diff --git a/docs/adr/0006-onion-layering-and-namespace-realignment.md b/docs/adr/0006-onion-layering-and-namespace-realignment.md new file mode 100644 index 000000000..979bf0c1f --- /dev/null +++ b/docs/adr/0006-onion-layering-and-namespace-realignment.md @@ -0,0 +1,76 @@ +# ADR-0006 — Onion layering + namespace realignment + +- **Status:** Proposed +- **Date:** 2026-05-31 +- **Deciders:** Fallout maintainers +- **Relates to:** ADR-[0004](0004-calendar-versioning-and-dual-pace-channels.md) (calendar versioning + channels — sets the breaking-change home), ADR-[0005](0005-ci-host-integration-ports-and-adapters.md) (the runtime-host ports the Application layer exposes), [docs/rebrand-plan.md](../rebrand-plan.md) (**amends** its deferral — see below) +- **Spike:** [docs/spikes/0002-onion-domain-ring.md](../spikes/0002-onion-domain-ring.md) + +## Context + +The project + namespace structure is inherited verbatim from NUKE: the rebrand ([#32](https://github.com/ChrisonSimtian/Fallout/issues/32)) did a **strict 1:1 prefix swap** (`Nuke.X` → `Fallout.X`) that deliberately preserved the existing shape. Two pathologies result: + +1. **Namespace ≠ project.** Most projects declare types under `Fallout.Common.*` rather than their own name. The core user-facing API (`FalloutBuild`, `Target`, `[Parameter]`, and the ADR-0005 ports) lives in namespace `Fallout.Common` inside project `Fallout.Build`. `Fallout.Core` declares both `Fallout.Core.Planning` and `Fallout.Common.Execution`. Every `Fallout.Utilities.*` sub-project (except `.Text.Yaml`) declares `Fallout.Common.*`. `Fallout.Tooling` → `Fallout.Common.Tooling`. `Fallout.Solution` (singular) → `Fallout.Solutions` (plural). `Fallout.Tooling.Generator` → `Fallout.CodeGeneration`. + +2. **`Fallout.Common.*` is a horizontal catch-all** contributed by **five** projects (`Build`, `Common`, `Core`, `Utilities`, `SourceGenerators`). It masks a layering that already exists *physically* — the `ProjectReference` graph is clean and acyclic (`Core`/`Utilities` at the bottom → `Build` → `Common` → `Cli` at the root) — but the namespaces lie about it. + +### Why now, and why this amends the rebrand plan + +`docs/rebrand-plan.md` explicitly **defers** "realigning project ↔ namespace" to "a future major version after the shim packages have sunset," citing the type-forwarding bridge as the blocker. This ADR **amends that deferral** on two grounds: + +- **The bridge is re-pointable.** It is not raw `[TypeForwardedTo]`; it is the `TransitionShimGenerator` (`ShimAllPublicTypesUnder(from, to)`), a *prefix-remappable* subclass generator. We can restructure the `Fallout.*` namespaces freely and re-point the shim mappings; the `Nuke.*` surface stays frozen and consumers on it are unaffected. +- **ADR-0004 gives a clean home.** Namespace realignment is breaking → it lands on `experimental` and is **batched to the `2027.0.0` yearly major**. The work happens in 2026; native `Fallout.*` consumers are carried by re-pointed shims + the `Fallout.Migrate` codefix. Old `Fallout.*` namespaces are deleted at the cut, not dragged. + +(The rebrand-plan is a transient maintainer-owned doc; this is a deliberate, recorded reversal of one of its deferrals, not a silent contradiction.) + +## Decision + +**Realign the runtime codebase to explicit onion layers, with `namespace = project = layer`.** + +| Layer | Holds | Today (selected) → target | +|---|---|---| +| **`Fallout.Domain.*`** (innermost; zero Fallout deps) | Target graph, planning algorithms, execution status, the read-only build model | `Fallout.Core` (`Fallout.Core.Planning` + `Fallout.Common.Execution`'s `ITargetModel`/`ExecutionStatus`) → `Fallout.Domain` | +| **`Fallout.Application.*`** (orchestration + ports; depends only on Domain) | `FalloutBuild`, `Target`, `[Parameter]` & attributes, execution engine (`BuildManager`/`Executor`/`Planner`), middleware pipeline, value injection, `Host` base, the ports (`IBuildHost`, `IBuildReporter`, `IConfigurationGenerator`, `IBuildServer`) | `Fallout.Build` + the `Fallout.Common`/`Fallout.Common.Execution`/`Fallout.Common.CI` types it declares → `Fallout.Application` | +| **`Fallout.Infrastructure.*`** (adapters; depends on Application/Domain) | CI host adapters, tool-execution framework + tool wrappers, IO/Net/compression/globbing/text, project/solution model, process execution | `Fallout.Common.CI.*` → `.Infrastructure.CI.*`; `Fallout.Common.Tools.*` + `Fallout.Tooling` → `.Infrastructure.Tools.*`; `Fallout.Utilities.*` → `.Infrastructure.IO`/`.Net`/`.Text.*`; `Fallout.ProjectModel`/`Fallout.Solution(s)` → `.Infrastructure.ProjectModel`/`.Solutions` | +| **`Fallout.Cli`** (composition root) | Entry points, host integration | `Fallout.Cli`, `Fallout.MSBuildTasks`, `Fallout.Migrate` — unchanged | + +**Outside the runtime onion** (build-time tooling, kept as-is): `Fallout.SourceGenerators`, `Fallout.Tooling.Generator` (the `Fallout.CodeGeneration` codegen), `Fallout.Migrate.Analyzers`. The vendored `Fallout.Persistence.Solution` keeps its namespace (Microsoft code). + +**Rules:** +1. **`namespace == project == layer`.** No project declares a namespace rooted outside its layer. `Fallout.Common` is **dissolved**. +2. **Onion dependency rule, fitness-enforced.** Domain references no other Fallout assembly; Application references only Domain; Infrastructure references Application/Domain; only the Cli composition root references Infrastructure. One architecture-test per ring, added as each ring lands (extends the ADR-0005 boundary-test pattern). +3. **Breaking → `experimental` → `2027.0.0`** (ADR-0004). Re-point the `TransitionShimGenerator` mappings so the `Nuke.*` surface is unchanged; update the `Fallout.Migrate` codefix to rewrite old `Fallout.*` → new `Fallout.*` for native consumers. Per ADR-0004 a breaking PR targets `experimental` only and carries `target/2027` + `breaking-change` + a `CHANGELOG.md` migration entry. +4. **Ring-by-ring migration**, inner to outer — each ring is its own PR on `experimental`. The spike (0002) proves the mechanics on the Domain ring before the larger rings. + +## Judgment calls (flagged for review — defaults chosen, easily changed) + +- **Public API under `.Infrastructure`.** Tool wrappers and CI attributes are consumer-facing yet land under `Fallout.Infrastructure.*` (the hexagonal reading: adapters are infrastructure even when public). Consequence: consumer `using` directives grow (`using Fallout.Infrastructure.Tools.DotNet;`). **Mitigation:** ship a curated set of global usings in the `dotnet fallout` project template so day-to-day build authoring isn't verbose. *Accepted by maintainer; recorded so the ergonomics cost is visible.* +- **`Fallout.Components`** (the `ICompile`/`IPack`/… mixins) sits on top of Application and is user-facing. Default: keep as `Fallout.Components` (an application-adjacent convenience layer that depends on Application). Alternative: fold into `Fallout.Application.Components`. +- **Utilities as Infrastructure vs shared kernel.** IO/Net/compression are genuinely infrastructure. Pure-algorithmic helpers (collections, reflection, string) are more a shared kernel; default is to keep a minimal shared-kernel that Domain may depend on, rather than forcing it through Infrastructure (which would violate the inner ring). To be pinned during the Application/Infrastructure rings. + +## Consequences + +### Positive +- The namespace finally tells the truth about the layer, and the onion dependency rule is enforced by construction (fitness tests), not convention. +- `Fallout.Common` — the catch-all — is gone; no more "five projects, one namespace." +- Sets the stage cleanly for the public plugin SDK (milestone #7): adapters live in a named Infrastructure layer behind the Application-owned ports. + +### Negative +- **Large, breaking, multi-PR.** Touches nearly every file's `namespace`/`using`. Mitigated by ring-by-ring sequencing, re-pointed shims, the migrate codefix, and batching to one yearly major. +- **Consumer churn** for native `Fallout.*` users (the `Nuke.*` shim users are insulated). The codefix + a CHANGELOG migration guide carry it. +- **`Fallout.Migrate`** must learn the intra-Fallout rename map in addition to the Nuke→Fallout one. + +## Alternatives considered + +- **Concern-aligned names** (`Fallout.Core`/`Build`/`Tooling`/`Tools`/`CI`/`IO`), namespace=project but not layer-labelled. Lower consumer surprise (keeps short names), fits the repo's by-concern convention. **Not chosen** — maintainer wants explicit onion layer names so the architecture is legible from the namespace alone. +- **Keep `Fallout.Common` as a thin facade** re-exporting the layered types. Less churn. **Not chosen** — keeps a name we want gone ("don't drag dead weight"). +- **Minimal — fix only the internal mismatches.** Smallest break. **Not chosen** — leaves the catch-all and doesn't achieve the onion goal. +- **Defer to "after shim sunset" (status quo per rebrand-plan).** **Not chosen** — see "Why now"; the work is wanted this year. + +## References + +- Current-state map: see the structural survey summarized in the spike. +- Shim machinery: `src/Fallout.SourceGenerators/TransitionShimGenerator.cs`, `src/Shims/Nuke.*` +- Migrate codefix: `src/Fallout.Migrate`, `src/Fallout.Migrate.Analyzers` +- Deferral being amended: `docs/rebrand-plan.md` §"What's deliberately deferred" +- Spike: [docs/spikes/0002-onion-domain-ring.md](../spikes/0002-onion-domain-ring.md) diff --git a/docs/adr/README.md b/docs/adr/README.md index c38f1e8d8..42a79817d 100644 --- a/docs/adr/README.md +++ b/docs/adr/README.md @@ -38,3 +38,4 @@ If you change a decision, do NOT silently rewrite the old ADR — add a new one | [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 | +| [0006](0006-onion-layering-and-namespace-realignment.md) | Onion layering + namespace realignment | Proposed | diff --git a/docs/spikes/0002-onion-domain-ring.md b/docs/spikes/0002-onion-domain-ring.md new file mode 100644 index 000000000..09e3acfa9 --- /dev/null +++ b/docs/spikes/0002-onion-domain-ring.md @@ -0,0 +1,52 @@ +# Spike 0002 — Onion realignment: prove the Domain ring + +- **Status:** Planned +- **Date:** 2026-05-31 +- **Decision record:** [ADR-0006](../adr/0006-onion-layering-and-namespace-realignment.md) +- **Channel:** `experimental` (spike branch `spike/onion-structure`) +- **Time-box:** one focused session + +> The full realignment (ADR-0006) is a large, breaking, ring-by-ring program. This spike does **only the innermost ring** — extract `Fallout.Domain` — to prove the three mechanics every later ring depends on, before committing to the larger ones. Throwaway-by-default. + +## Hypothesis + +> The pure-domain types can be lifted into a `Fallout.Domain` layer (`namespace = project = layer`) such that: (1) the Domain assembly references **no other Fallout assembly**, enforced by a fitness test; (2) the `TransitionShimGenerator` can be **re-pointed** so the `Nuke.*` surface is byte-for-byte unchanged; (3) the solution still builds and all tests pass — confirming the rename + shim-repoint + fitness loop that the Application and Infrastructure rings will reuse at larger scale. + +## Scope + +### In scope +1. **Create `Fallout.Domain`** (project + namespace). Move into it the pure-domain types: `Fallout.Core.Planning.*` (graph/topo-sort/cycle detection) and the domain types currently under `Fallout.Common.Execution` in the `Fallout.Core` project (`ITargetModel`, `ExecutionStatus`). Result: `Fallout.Core` is renamed/absorbed; no domain type remains under `Fallout.Common.*`. +2. **Re-point the shim.** Update the `ShimAllPublicTypesUnder` mappings so the moved types still surface at their original `Nuke.*` names (e.g. `Fallout.Domain.ITargetModel` → `Nuke.Common.Execution.ITargetModel`). Confirm the generated `Nuke.*` surface is unchanged. +3. **Fitness test.** Assert `Fallout.Domain` references no other `Fallout.*` assembly (the innermost-ring invariant). Demonstrate it fails on an injected violation, then revert. +4. **Update internal references.** `Fallout.Application`-to-be (currently `Fallout.Build`) and others that consumed the moved types via `Fallout.Common.Execution` now reference `Fallout.Domain`. + +### Out of scope +- ❌ The Application and Infrastructure rings (later PRs). +- ❌ Renaming `Fallout.Build` → `Fallout.Application`, dissolving `Fallout.Common`, moving tools/CI/utilities — none of it yet. +- ❌ Updating the `Fallout.Migrate` codefix for native consumers (batched work; the spike only proves the shim path). +- ❌ Consumer-template global usings (an Application/Infrastructure-ring concern). + +## Ordered steps + +1. Branch is `spike/onion-structure` off `experimental`. (Done.) +2. Read the current `Fallout.Core` contents and every reference to its types across the solution (esp. `Fallout.Common.Execution` consumers in `Fallout.Build`). +3. Create `Fallout.Domain` project; move the pure-domain files; set `namespace = Fallout.Domain[.Planning]`. +4. Fix references (project refs + `using`s) across the solution. +5. Re-point the `TransitionShimGenerator` mapping(s); rebuild the `Nuke.*` shim and confirm its public surface is unchanged (diff the generated types, or assert key `Nuke.Common.Execution.*` types still resolve). +6. Add the Domain-ring fitness test; confirm green, then confirm it goes red on a deliberate `Fallout.*` reference, then revert. +7. Validate: `./build.sh Compile` + `./build.sh Test` (or the per-project equivalents) green; dogfood workflows unchanged. + +## Success criteria +- ✅ `Fallout.Domain` exists; no domain type remains under `Fallout.Common.*`. +- ✅ Fitness test passes and demonstrably fails on a violation. +- ✅ `Nuke.*` shim surface unchanged (consumers on the bridge unaffected). +- ✅ Solution builds; tests green. +- ✅ A written verdict: did the rename + shim-repoint + fitness loop hold? What surprised us? Is it safe to scale to the Application ring (the big one — the user-facing API rename)? + +## Risks / watch +- **Shim re-point is the real unknown.** If `ShimAllPublicTypesUnder` can't express the many-old-layers → one-`Nuke.Common` mapping cleanly, that's the key finding — it gates every later ring. Record precisely what the generator supports. +- **`Fallout.Core` already mixes `Fallout.Core.Planning` and `Fallout.Common.Execution`** — make sure only genuinely-pure types move; anything touching execution *orchestration* (not the model) belongs in the Application ring, not Domain. +- **Hidden inward references.** A "domain" type that secretly reaches into utilities/IO would break the zero-deps invariant — the fitness test will catch it; treat any such case as a finding about what's really domain. + +## Exit +Set **Status: Done**, append the verdict, and feed it back into ADR-0006 (confirm/adjust the layer mapping, then promote `Proposed` → `Accepted`) before starting the Application ring. From 607bbf0c171d7900b2be838334c7689b88167e3b Mon Sep 17 00:00:00 2001 From: Chrison Simtian Date: Sun, 31 May 2026 20:31:45 +1200 Subject: [PATCH 2/6] =?UTF-8?q?docs:=20refine=20ADR-0006/spike-0002=20?= =?UTF-8?q?=E2=80=94=20Components=20reasoning=20+=20defer=20shim=20strateg?= =?UTF-8?q?y?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 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 --- ...nion-layering-and-namespace-realignment.md | 16 +++++++---- docs/spikes/0002-onion-domain-ring.md | 27 +++++++++---------- 2 files changed, 23 insertions(+), 20 deletions(-) diff --git a/docs/adr/0006-onion-layering-and-namespace-realignment.md b/docs/adr/0006-onion-layering-and-namespace-realignment.md index 979bf0c1f..2bcfe014c 100644 --- a/docs/adr/0006-onion-layering-and-namespace-realignment.md +++ b/docs/adr/0006-onion-layering-and-namespace-realignment.md @@ -18,7 +18,7 @@ The project + namespace structure is inherited verbatim from NUKE: the rebrand ( `docs/rebrand-plan.md` explicitly **defers** "realigning project ↔ namespace" to "a future major version after the shim packages have sunset," citing the type-forwarding bridge as the blocker. This ADR **amends that deferral** on two grounds: -- **The bridge is re-pointable.** It is not raw `[TypeForwardedTo]`; it is the `TransitionShimGenerator` (`ShimAllPublicTypesUnder(from, to)`), a *prefix-remappable* subclass generator. We can restructure the `Fallout.*` namespaces freely and re-point the shim mappings; the `Nuke.*` surface stays frozen and consumers on it are unaffected. +- **The bridge is not a blocker.** The rebrand-plan deferred this work *because* it assumed a rigid `[TypeForwardedTo]` bridge. The actual machinery is the `TransitionShimGenerator` (`ShimAllPublicTypesUnder(from, to)`), a *prefix-remappable* subclass generator — so restructuring `Fallout.*` doesn't orphan the `Nuke.*` surface in principle. We nonetheless **defer the migration strategy wholesale** (see Decision §"Migration & shim strategy") rather than re-point ring-by-ring; the point here is only that the stated blocker doesn't bind. - **ADR-0004 gives a clean home.** Namespace realignment is breaking → it lands on `experimental` and is **batched to the `2027.0.0` yearly major**. The work happens in 2026; native `Fallout.*` consumers are carried by re-pointed shims + the `Fallout.Migrate` codefix. Old `Fallout.*` namespaces are deleted at the cut, not dragged. (The rebrand-plan is a transient maintainer-owned doc; this is a deliberate, recorded reversal of one of its deferrals, not a silent contradiction.) @@ -39,13 +39,20 @@ The project + namespace structure is inherited verbatim from NUKE: the rebrand ( **Rules:** 1. **`namespace == project == layer`.** No project declares a namespace rooted outside its layer. `Fallout.Common` is **dissolved**. 2. **Onion dependency rule, fitness-enforced.** Domain references no other Fallout assembly; Application references only Domain; Infrastructure references Application/Domain; only the Cli composition root references Infrastructure. One architecture-test per ring, added as each ring lands (extends the ADR-0005 boundary-test pattern). -3. **Breaking → `experimental` → `2027.0.0`** (ADR-0004). Re-point the `TransitionShimGenerator` mappings so the `Nuke.*` surface is unchanged; update the `Fallout.Migrate` codefix to rewrite old `Fallout.*` → new `Fallout.*` for native consumers. Per ADR-0004 a breaking PR targets `experimental` only and carries `target/2027` + `breaking-change` + a `CHANGELOG.md` migration entry. +3. **Breaking → `experimental` → `2027.0.0`** (ADR-0004). A breaking PR targets `experimental` only and carries `target/2027` + `breaking-change` + a `CHANGELOG.md` migration entry. 4. **Ring-by-ring migration**, inner to outer — each ring is its own PR on `experimental`. The spike (0002) proves the mechanics on the Domain ring before the larger rings. +5. **Migration/shim strategy is deferred wholesale** — see below. + +### Migration & shim strategy: deferred by design + +The `Nuke.*` transition shims (and any native-`Fallout.*` migration aid) are **explicitly out of scope for the rearchitecture rings.** Rationale: there's no point re-pointing the existing `TransitionShimGenerator` ring-by-ring toward a target that's still moving. Instead — once the final layered shape has settled — we design a **fresh migration/shim strategy that fits whatever we ended up with**, as its own phase and its own ADR. The existing bridge is re-pointable (it's a prefix-remappable subclass generator, not raw `[TypeForwardedTo]`), so this deferral costs us no future optionality; it's a sequencing choice, not a capability loss. + +Consequence during the work: on `experimental`, `Nuke.*` shim parity is **not maintained** while the rings land. That's acceptable — `experimental` is the unstable lane, and `2027.0.0` is the only deadline that matters; the new migration story is built before the cut. `Fallout.Migrate` likewise gets revisited then, not incrementally. ## Judgment calls (flagged for review — defaults chosen, easily changed) - **Public API under `.Infrastructure`.** Tool wrappers and CI attributes are consumer-facing yet land under `Fallout.Infrastructure.*` (the hexagonal reading: adapters are infrastructure even when public). Consequence: consumer `using` directives grow (`using Fallout.Infrastructure.Tools.DotNet;`). **Mitigation:** ship a curated set of global usings in the `dotnet fallout` project template so day-to-day build authoring isn't verbose. *Accepted by maintainer; recorded so the ergonomics cost is visible.* -- **`Fallout.Components`** (the `ICompile`/`IPack`/… mixins) sits on top of Application and is user-facing. Default: keep as `Fallout.Components` (an application-adjacent convenience layer that depends on Application). Alternative: fold into `Fallout.Application.Components`. +- **`Fallout.Components`** (the `ICompile`/`IPack`/… mixins) is a **recipes layer that sits *outside* Infrastructure**, not inside Application. The mixins call tool wrappers directly (`ICompile.Compile` → `DotNetBuild(...)` from `Fallout.Common.Tools.DotNet`), so `Components → Infrastructure`. It therefore **cannot** be folded into `Application` without inverting the onion (the inner ring would transitively depend on Infrastructure — the exact thing the fitness tests forbid). Default: keep as `Fallout.Components`, an outer recipes layer depending on Application + Infrastructure, near the composition root. *Folding it into `Application` is possible only by first porting tool execution behind an Application-owned tool port (the ADR-0005 move applied to tooling) — a separate, larger future step, not bundled here.* - **Utilities as Infrastructure vs shared kernel.** IO/Net/compression are genuinely infrastructure. Pure-algorithmic helpers (collections, reflection, string) are more a shared kernel; default is to keep a minimal shared-kernel that Domain may depend on, rather than forcing it through Infrastructure (which would violate the inner ring). To be pinned during the Application/Infrastructure rings. ## Consequences @@ -57,8 +64,7 @@ The project + namespace structure is inherited verbatim from NUKE: the rebrand ( ### Negative - **Large, breaking, multi-PR.** Touches nearly every file's `namespace`/`using`. Mitigated by ring-by-ring sequencing, re-pointed shims, the migrate codefix, and batching to one yearly major. -- **Consumer churn** for native `Fallout.*` users (the `Nuke.*` shim users are insulated). The codefix + a CHANGELOG migration guide carry it. -- **`Fallout.Migrate`** must learn the intra-Fallout rename map in addition to the Nuke→Fallout one. +- **Consumer churn** for native `Fallout.*` users, and a window on `experimental` where `Nuke.*` shim parity lapses. Both are carried by the deferred migration phase (a fresh strategy + CHANGELOG migration guide), built before the `2027.0.0` cut — not during the rings. ## Alternatives considered diff --git a/docs/spikes/0002-onion-domain-ring.md b/docs/spikes/0002-onion-domain-ring.md index 09e3acfa9..1d483278a 100644 --- a/docs/spikes/0002-onion-domain-ring.md +++ b/docs/spikes/0002-onion-domain-ring.md @@ -10,20 +10,19 @@ ## Hypothesis -> The pure-domain types can be lifted into a `Fallout.Domain` layer (`namespace = project = layer`) such that: (1) the Domain assembly references **no other Fallout assembly**, enforced by a fitness test; (2) the `TransitionShimGenerator` can be **re-pointed** so the `Nuke.*` surface is byte-for-byte unchanged; (3) the solution still builds and all tests pass — confirming the rename + shim-repoint + fitness loop that the Application and Infrastructure rings will reuse at larger scale. +> The pure-domain types can be lifted into a `Fallout.Domain` layer (`namespace = project = layer`) such that: (1) the Domain assembly references **no other Fallout assembly**, enforced by a fitness test; (2) the solution still builds and all tests pass — confirming the move + reference-fixup + fitness loop that the Application and Infrastructure rings will reuse at larger scale. ## Scope ### In scope 1. **Create `Fallout.Domain`** (project + namespace). Move into it the pure-domain types: `Fallout.Core.Planning.*` (graph/topo-sort/cycle detection) and the domain types currently under `Fallout.Common.Execution` in the `Fallout.Core` project (`ITargetModel`, `ExecutionStatus`). Result: `Fallout.Core` is renamed/absorbed; no domain type remains under `Fallout.Common.*`. -2. **Re-point the shim.** Update the `ShimAllPublicTypesUnder` mappings so the moved types still surface at their original `Nuke.*` names (e.g. `Fallout.Domain.ITargetModel` → `Nuke.Common.Execution.ITargetModel`). Confirm the generated `Nuke.*` surface is unchanged. -3. **Fitness test.** Assert `Fallout.Domain` references no other `Fallout.*` assembly (the innermost-ring invariant). Demonstrate it fails on an injected violation, then revert. -4. **Update internal references.** `Fallout.Application`-to-be (currently `Fallout.Build`) and others that consumed the moved types via `Fallout.Common.Execution` now reference `Fallout.Domain`. +2. **Fitness test.** Assert `Fallout.Domain` references no other `Fallout.*` assembly (the innermost-ring invariant). Demonstrate it fails on an injected violation, then revert. +3. **Update internal references.** `Fallout.Application`-to-be (currently `Fallout.Build`) and others that consumed the moved types via `Fallout.Common.Execution` now reference `Fallout.Domain`. ### Out of scope - ❌ The Application and Infrastructure rings (later PRs). - ❌ Renaming `Fallout.Build` → `Fallout.Application`, dissolving `Fallout.Common`, moving tools/CI/utilities — none of it yet. -- ❌ Updating the `Fallout.Migrate` codefix for native consumers (batched work; the spike only proves the shim path). +- ❌ **Shim / migration parity — deferred wholesale** (ADR-0006). We do *not* re-point the `TransitionShimGenerator` here; the `Nuke.*` surface is allowed to lapse on `experimental`. The migration strategy is redesigned as its own phase once the target shape settles. - ❌ Consumer-template global usings (an Application/Infrastructure-ring concern). ## Ordered steps @@ -31,22 +30,20 @@ 1. Branch is `spike/onion-structure` off `experimental`. (Done.) 2. Read the current `Fallout.Core` contents and every reference to its types across the solution (esp. `Fallout.Common.Execution` consumers in `Fallout.Build`). 3. Create `Fallout.Domain` project; move the pure-domain files; set `namespace = Fallout.Domain[.Planning]`. -4. Fix references (project refs + `using`s) across the solution. -5. Re-point the `TransitionShimGenerator` mapping(s); rebuild the `Nuke.*` shim and confirm its public surface is unchanged (diff the generated types, or assert key `Nuke.Common.Execution.*` types still resolve). -6. Add the Domain-ring fitness test; confirm green, then confirm it goes red on a deliberate `Fallout.*` reference, then revert. -7. Validate: `./build.sh Compile` + `./build.sh Test` (or the per-project equivalents) green; dogfood workflows unchanged. +4. Fix references (project refs + `using`s) across the solution. The `Nuke.*` shim build may break here — that's expected and allowed (shim parity deferred); if it blocks the solution build, temporarily exclude the affected shim project rather than re-pointing it. +5. Add the Domain-ring fitness test; confirm green, then confirm it goes red on a deliberate `Fallout.*` reference, then revert. +6. Validate: `./build.sh Compile` + `./build.sh Test` (or the per-project equivalents) green; dogfood workflows unchanged. ## Success criteria - ✅ `Fallout.Domain` exists; no domain type remains under `Fallout.Common.*`. - ✅ Fitness test passes and demonstrably fails on a violation. -- ✅ `Nuke.*` shim surface unchanged (consumers on the bridge unaffected). -- ✅ Solution builds; tests green. -- ✅ A written verdict: did the rename + shim-repoint + fitness loop hold? What surprised us? Is it safe to scale to the Application ring (the big one — the user-facing API rename)? +- ✅ Solution (excluding deferred shim parity) builds; tests green. +- ✅ A written verdict: did the move + reference-fixup + fitness loop hold? What surprised us? Is it safe to scale to the Application ring (the big one — the user-facing API rename)? ## Risks / watch -- **Shim re-point is the real unknown.** If `ShimAllPublicTypesUnder` can't express the many-old-layers → one-`Nuke.Common` mapping cleanly, that's the key finding — it gates every later ring. Record precisely what the generator supports. -- **`Fallout.Core` already mixes `Fallout.Core.Planning` and `Fallout.Common.Execution`** — make sure only genuinely-pure types move; anything touching execution *orchestration* (not the model) belongs in the Application ring, not Domain. -- **Hidden inward references.** A "domain" type that secretly reaches into utilities/IO would break the zero-deps invariant — the fitness test will catch it; treat any such case as a finding about what's really domain. +- **What's *really* domain.** `Fallout.Core` already mixes `Fallout.Core.Planning` and `Fallout.Common.Execution` — move only genuinely-pure types; anything touching execution *orchestration* (not the model) belongs in the Application ring, not Domain. +- **Hidden inward references.** A "domain" type that secretly reaches into utilities/IO would break the zero-deps invariant — the fitness test will catch it; treat any such case as a finding about what's really domain. This is now the spike's primary unknown (the shim question is deferred out). +- **Shim fallout is expected, not a failure.** If moving types breaks the `Nuke.*` shim build, that's the deferred concern surfacing — note it and move on; do not rabbit-hole on re-pointing. ## Exit Set **Status: Done**, append the verdict, and feed it back into ADR-0006 (confirm/adjust the layer mapping, then promote `Proposed` → `Accepted`) before starting the Application ring. From 43e36ba5a8556d9e117e4dc0bb1854abc06a2c96 Mon Sep 17 00:00:00 2001 From: Chrison Simtian Date: Sun, 31 May 2026 21:05:50 +1200 Subject: [PATCH 3/6] =?UTF-8?q?docs:=20ADR-0006=20=E2=80=94=20tool=20wrapp?= =?UTF-8?q?ers=20are=20Application=20vocabulary,=20execution=20is=20the=20?= =?UTF-8?q?port?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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 --- ...onion-layering-and-namespace-realignment.md | 18 +++++++++++++++--- 1 file changed, 15 insertions(+), 3 deletions(-) diff --git a/docs/adr/0006-onion-layering-and-namespace-realignment.md b/docs/adr/0006-onion-layering-and-namespace-realignment.md index 2bcfe014c..c0f01f7f4 100644 --- a/docs/adr/0006-onion-layering-and-namespace-realignment.md +++ b/docs/adr/0006-onion-layering-and-namespace-realignment.md @@ -30,8 +30,10 @@ The project + namespace structure is inherited verbatim from NUKE: the rebrand ( | Layer | Holds | Today (selected) → target | |---|---|---| | **`Fallout.Domain.*`** (innermost; zero Fallout deps) | Target graph, planning algorithms, execution status, the read-only build model | `Fallout.Core` (`Fallout.Core.Planning` + `Fallout.Common.Execution`'s `ITargetModel`/`ExecutionStatus`) → `Fallout.Domain` | -| **`Fallout.Application.*`** (orchestration + ports; depends only on Domain) | `FalloutBuild`, `Target`, `[Parameter]` & attributes, execution engine (`BuildManager`/`Executor`/`Planner`), middleware pipeline, value injection, `Host` base, the ports (`IBuildHost`, `IBuildReporter`, `IConfigurationGenerator`, `IBuildServer`) | `Fallout.Build` + the `Fallout.Common`/`Fallout.Common.Execution`/`Fallout.Common.CI` types it declares → `Fallout.Application` | -| **`Fallout.Infrastructure.*`** (adapters; depends on Application/Domain) | CI host adapters, tool-execution framework + tool wrappers, IO/Net/compression/globbing/text, project/solution model, process execution | `Fallout.Common.CI.*` → `.Infrastructure.CI.*`; `Fallout.Common.Tools.*` + `Fallout.Tooling` → `.Infrastructure.Tools.*`; `Fallout.Utilities.*` → `.Infrastructure.IO`/`.Net`/`.Text.*`; `Fallout.ProjectModel`/`Fallout.Solution(s)` → `.Infrastructure.ProjectModel`/`.Solutions` | +| **`Fallout.Application.*`** (orchestration + ports + vocabulary; depends only on Domain) | `FalloutBuild`, `Target`, `[Parameter]` & attributes, execution engine (`BuildManager`/`Executor`/`Planner`), middleware pipeline, value injection, `Host` base; the ports (`IBuildHost`, `IBuildReporter`, `IConfigurationGenerator`, `IBuildServer`, **and a process/tool-execution port**); the **typed tool vocabulary** (`Options`/`ToolOptions` model + the generated wrappers like `DotNetTasks` & settings — pure command builders); and the **`Fallout.Components` recipes** | `Fallout.Build` + the `Fallout.Common*` it declares; `Fallout.Common.Tools.*` (wrappers) + the pure part of `Fallout.Tooling`; `Fallout.Components` → `Fallout.Application.*` | +| **`Fallout.Infrastructure.*`** (I/O adapters behind the ports; depends on Application/Domain) | The adapters that actually touch the outside world: the **process/tool runner** (OS process spawn), filesystem, HTTP, tool-path/package resolvers, **CI host adapters**, config-file writers, project/solution readers | `Fallout.Common.CI.*` → `.Infrastructure.CI.*`; the executor part of `Fallout.Tooling` (`ProcessTasks`/`ToolExecutor`) + `Fallout.Utilities.IO`/`.Net`/`.Compression`/`.Globbing` → `.Infrastructure.*`; `Fallout.ProjectModel`/`Fallout.Solution(s)` → `.Infrastructure.ProjectModel`/`.Solutions` | + +**The tool layer splits along purity** (decided after reviewing `DotNetBuild`'s shape — see ADR-0005's ports pattern applied to the deepest I/O): the **command vocabulary** (`DotNetBuildSettings : ToolOptions` is pure data; `DotNetBuild` just constructs argv) is Application; the **one impure step** — `ProcessTasks.StartProcess` spawning a real OS process (today a *static* call) — becomes an injectable **process/tool-execution port** with the OS adapter in Infrastructure. This is what lets `Fallout.Components` (e.g. `ICompile` calling `DotNetBuild`) live in Application without the inner ring depending on a concrete — and it makes builds unit-testable by faking the runner. There is deliberately **no** generic "abstract build" port: `ICompile` is irreducibly DotNet-specific, so abstracting it would be anemic or a re-spelling of DotNet — the wrong abstraction. The seam is *execution*, not *build semantics*. | **`Fallout.Cli`** (composition root) | Entry points, host integration | `Fallout.Cli`, `Fallout.MSBuildTasks`, `Fallout.Migrate` — unchanged | **Outside the runtime onion** (build-time tooling, kept as-is): `Fallout.SourceGenerators`, `Fallout.Tooling.Generator` (the `Fallout.CodeGeneration` codegen), `Fallout.Migrate.Analyzers`. The vendored `Fallout.Persistence.Solution` keeps its namespace (Microsoft code). @@ -49,10 +51,20 @@ The `Nuke.*` transition shims (and any native-`Fallout.*` migration aid) are **e Consequence during the work: on `experimental`, `Nuke.*` shim parity is **not maintained** while the rings land. That's acceptable — `experimental` is the unstable lane, and `2027.0.0` is the only deadline that matters; the new migration story is built before the cut. `Fallout.Migrate` likewise gets revisited then, not incrementally. +### Sequence (each step a PR on `experimental`, with its own ring fitness test) + +1. **Domain ring** — extract `Fallout.Domain` (spike [0002](../spikes/0002-onion-domain-ring.md)). Innermost, smallest; proves the move → reference-fixup → fitness loop. +2. **Application ring** — rename `Fallout.Build` → `Fallout.Application` and dissolve the `Fallout.Common` core API into it (the user-facing `FalloutBuild`/`Target`/`[Parameter]` rename — the big one). +3. **Tooling/execution-port spike** — extract the process/tool-execution port; split `Fallout.Tooling` into pure vocabulary (Application) + executor adapter (Infrastructure). Unlocks tool wrappers as Application vocabulary and is a standalone testability win. *(Can run in parallel with step 2; must precede step 4.)* +4. **Tool vocabulary + `Components` → Application** — move the wrappers and the `Fallout.Components` recipes in, now that execution is behind the port. +5. **Infrastructure ring** — CI host adapters, IO/Net/compression/globbing, path/package resolvers, project/solution readers → `Fallout.Infrastructure.*`. + +Migration/shim strategy is redesigned after step 5, before the cut (deferred, above). + ## Judgment calls (flagged for review — defaults chosen, easily changed) - **Public API under `.Infrastructure`.** Tool wrappers and CI attributes are consumer-facing yet land under `Fallout.Infrastructure.*` (the hexagonal reading: adapters are infrastructure even when public). Consequence: consumer `using` directives grow (`using Fallout.Infrastructure.Tools.DotNet;`). **Mitigation:** ship a curated set of global usings in the `dotnet fallout` project template so day-to-day build authoring isn't verbose. *Accepted by maintainer; recorded so the ergonomics cost is visible.* -- **`Fallout.Components`** (the `ICompile`/`IPack`/… mixins) is a **recipes layer that sits *outside* Infrastructure**, not inside Application. The mixins call tool wrappers directly (`ICompile.Compile` → `DotNetBuild(...)` from `Fallout.Common.Tools.DotNet`), so `Components → Infrastructure`. It therefore **cannot** be folded into `Application` without inverting the onion (the inner ring would transitively depend on Infrastructure — the exact thing the fitness tests forbid). Default: keep as `Fallout.Components`, an outer recipes layer depending on Application + Infrastructure, near the composition root. *Folding it into `Application` is possible only by first porting tool execution behind an Application-owned tool port (the ADR-0005 move applied to tooling) — a separate, larger future step, not bundled here.* +- **`Fallout.Components` → Application (RESOLVED, not an open default).** The mixins call tool wrappers (`ICompile.Compile` → `DotNetBuild`). Rather than exile `Components` to an outer ring, we **invert the dependency**: the tool wrappers become Application *vocabulary* and the one impure step (process spawn) moves behind the process/tool-execution port (see the layer table). `Components` then depends only on Application vocabulary + that port and lives in `Application` cleanly. **Prerequisite:** the tooling/execution-port spike (step 3 of the Sequence) lands before the wrappers + `Components` move. - **Utilities as Infrastructure vs shared kernel.** IO/Net/compression are genuinely infrastructure. Pure-algorithmic helpers (collections, reflection, string) are more a shared kernel; default is to keep a minimal shared-kernel that Domain may depend on, rather than forcing it through Infrastructure (which would violate the inner ring). To be pinned during the Application/Infrastructure rings. ## Consequences From f546910d9b9bef0530034526db86cb9b4b7ec266 Mon Sep 17 00:00:00 2001 From: Chrison Simtian Date: Sun, 31 May 2026 21:20:05 +1200 Subject: [PATCH 4/6] refactor(arch)!: extract Fallout.Domain ring (onion realignment step 1) 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 --- fallout.slnx | 4 +- src/Fallout.Build/Execution/BuildExecutor.cs | 1 + src/Fallout.Build/Execution/BuildManager.cs | 1 + .../Execution/ExecutableTarget.cs | 3 +- .../Execution/ExecutionPlanner.cs | 5 ++- .../Execution/ExecutionStatus.cs | 4 +- src/Fallout.Build/Fallout.Build.csproj | 2 +- src/Fallout.Build/FalloutBuild.cs | 1 + src/Fallout.Build/Host.cs | 1 + .../Telemetry/Telemetry.Events.cs | 1 + .../Execution/ExecutionStatus.cs | 2 +- .../Execution/ITargetModel.cs | 2 +- .../Fallout.Domain.csproj} | 0 .../Graph/StronglyConnectedComponent.cs | 2 +- .../Graph/StronglyConnectedComponentFinder.cs | 2 +- .../Graph/StronglyConnectedComponentList.cs | 2 +- .../Planning/Graph/Vertex.cs | 2 +- .../Planning/TopoSort.cs | 2 +- .../README.md | 0 .../Fallout.Build.Tests/BuildExecutorTest.cs | 1 + .../ArchitectureFitnessTests.cs | 37 +++++++++++-------- .../Fallout.Domain.Tests.csproj} | 2 +- .../TopoSortTests.cs | 4 +- ...nGeneratorTest.Test#Solution.g.verified.cs | 4 +- 24 files changed, 49 insertions(+), 36 deletions(-) rename src/{Fallout.Core => Fallout.Domain}/Execution/ExecutionStatus.cs (80%) rename src/{Fallout.Core => Fallout.Domain}/Execution/ITargetModel.cs (97%) rename src/{Fallout.Core/Fallout.Core.csproj => Fallout.Domain/Fallout.Domain.csproj} (100%) rename src/{Fallout.Core => Fallout.Domain}/Planning/Graph/StronglyConnectedComponent.cs (94%) rename src/{Fallout.Core => Fallout.Domain}/Planning/Graph/StronglyConnectedComponentFinder.cs (97%) rename src/{Fallout.Core => Fallout.Domain}/Planning/Graph/StronglyConnectedComponentList.cs (96%) rename src/{Fallout.Core => Fallout.Domain}/Planning/Graph/Vertex.cs (95%) rename src/{Fallout.Core => Fallout.Domain}/Planning/TopoSort.cs (99%) rename src/{Fallout.Core => Fallout.Domain}/README.md (100%) rename tests/{Fallout.Core.Tests => Fallout.Domain.Tests}/ArchitectureFitnessTests.cs (54%) rename tests/{Fallout.Core.Tests/Fallout.Core.Tests.csproj => Fallout.Domain.Tests/Fallout.Domain.Tests.csproj} (74%) rename tests/{Fallout.Core.Tests => Fallout.Domain.Tests}/TopoSortTests.cs (97%) diff --git a/fallout.slnx b/fallout.slnx index 0f6ae9699..ad895155c 100644 --- a/fallout.slnx +++ b/fallout.slnx @@ -13,7 +13,7 @@ - + @@ -37,7 +37,7 @@ - + diff --git a/src/Fallout.Build/Execution/BuildExecutor.cs b/src/Fallout.Build/Execution/BuildExecutor.cs index 5f8869dae..b1cb733ee 100644 --- a/src/Fallout.Build/Execution/BuildExecutor.cs +++ b/src/Fallout.Build/Execution/BuildExecutor.cs @@ -5,6 +5,7 @@ using Fallout.Common.IO; using Fallout.Common.Utilities; using Fallout.Common.Utilities.Collections; +using Fallout.Domain.Execution; using Serilog; namespace Fallout.Common.Execution; diff --git a/src/Fallout.Build/Execution/BuildManager.cs b/src/Fallout.Build/Execution/BuildManager.cs index 5925fceca..f0d8daefc 100644 --- a/src/Fallout.Build/Execution/BuildManager.cs +++ b/src/Fallout.Build/Execution/BuildManager.cs @@ -8,6 +8,7 @@ using Fallout.Common.Tooling; using Fallout.Common.Utilities; using Fallout.Common.Utilities.Collections; +using Fallout.Domain.Execution; using Serilog; #pragma warning disable CA2255 diff --git a/src/Fallout.Build/Execution/ExecutableTarget.cs b/src/Fallout.Build/Execution/ExecutableTarget.cs index c4a9501c2..64bcde8c0 100644 --- a/src/Fallout.Build/Execution/ExecutableTarget.cs +++ b/src/Fallout.Build/Execution/ExecutableTarget.cs @@ -6,6 +6,7 @@ using System.Reflection; using Fallout.Common.Tooling; using Fallout.Common.Utilities.Collections; +using Fallout.Domain.Execution; // ReSharper disable MissingBaseTypeHighlighting namespace Fallout.Common.Execution; @@ -62,7 +63,7 @@ public string OnlyWhen set => SummaryInformation.AddPairWhenValueNotNull(nameof(OnlyWhen), value); } - // ITargetModel — read-only projection over the live dependency lists for Fallout.Core consumers. + // ITargetModel — read-only projection over the live dependency lists for Fallout.Domain consumers. IReadOnlyCollection ITargetModel.ExecutionDependencyNames => ExecutionDependencies.Select(x => x.Name).ToList(); IReadOnlyCollection ITargetModel.OrderDependencyNames => OrderDependencies.Select(x => x.Name).ToList(); IReadOnlyCollection ITargetModel.TriggerNames => Triggers.Select(x => x.Name).ToList(); diff --git a/src/Fallout.Build/Execution/ExecutionPlanner.cs b/src/Fallout.Build/Execution/ExecutionPlanner.cs index 67d3f6cd9..16450deca 100644 --- a/src/Fallout.Build/Execution/ExecutionPlanner.cs +++ b/src/Fallout.Build/Execution/ExecutionPlanner.cs @@ -3,7 +3,8 @@ using System.Linq; using Fallout.Common.Utilities; using Fallout.Common.Utilities.Collections; -using Fallout.Core.Planning; +using Fallout.Domain.Execution; +using Fallout.Domain.Planning; namespace Fallout.Common.Execution; @@ -39,7 +40,7 @@ private static IReadOnlyCollection GetExecutionPlanInternal( IReadOnlyCollection executableTargets, ICollection invokedTargets) { - // Pure graph work — cycle detection and topological ordering — lives in Fallout.Core. + // Pure graph work — cycle detection and topological ordering — lives in Fallout.Domain. // Everything below is orchestration: deciding to fail the build, and applying the domain // scheduling rules (invoked / default / execution-dependency) over the ordered nodes. var strict = ParameterService.GetNamedArgument("strict"); diff --git a/src/Fallout.Build/Execution/ExecutionStatus.cs b/src/Fallout.Build/Execution/ExecutionStatus.cs index f98f50e1d..cecef623f 100644 --- a/src/Fallout.Build/Execution/ExecutionStatus.cs +++ b/src/Fallout.Build/Execution/ExecutionStatus.cs @@ -1,6 +1,6 @@ using System.Runtime.CompilerServices; // Moved to Fallout.Core in v11 (issue #88). Forwarded so existing consumers that reference -// Fallout.Build keep resolving Fallout.Common.Execution.ExecutionStatus without a recompile. +// Fallout.Build keep resolving Fallout.Domain.Execution.ExecutionStatus without a recompile. // The forwarder can be dropped in 12.0 once the plugin SDK is the canonical reference. -[assembly: TypeForwardedTo(typeof(Fallout.Common.Execution.ExecutionStatus))] +[assembly: TypeForwardedTo(typeof(Fallout.Domain.Execution.ExecutionStatus))] diff --git a/src/Fallout.Build/Fallout.Build.csproj b/src/Fallout.Build/Fallout.Build.csproj index ea2b968c3..530b59d60 100644 --- a/src/Fallout.Build/Fallout.Build.csproj +++ b/src/Fallout.Build/Fallout.Build.csproj @@ -6,7 +6,7 @@ - + diff --git a/src/Fallout.Build/FalloutBuild.cs b/src/Fallout.Build/FalloutBuild.cs index af8573875..9f00da1f6 100644 --- a/src/Fallout.Build/FalloutBuild.cs +++ b/src/Fallout.Build/FalloutBuild.cs @@ -6,6 +6,7 @@ using Fallout.Build.Execution.Extensions; using Fallout.Common.CI; using Fallout.Common.Execution; +using Fallout.Domain.Execution; using Fallout.Common.IO; using Fallout.Common.Tooling; using Fallout.Common.Utilities; diff --git a/src/Fallout.Build/Host.cs b/src/Fallout.Build/Host.cs index b5e147813..413cb6bb9 100644 --- a/src/Fallout.Build/Host.cs +++ b/src/Fallout.Build/Host.cs @@ -3,6 +3,7 @@ using System.Globalization; using System.Linq; using Fallout.Common.Execution; +using Fallout.Domain.Execution; using Fallout.Common.Execution.Theming; using Fallout.Common.Utilities; using Fallout.Common.Utilities.Collections; diff --git a/src/Fallout.Build/Telemetry/Telemetry.Events.cs b/src/Fallout.Build/Telemetry/Telemetry.Events.cs index abe85de67..ee01b590e 100644 --- a/src/Fallout.Build/Telemetry/Telemetry.Events.cs +++ b/src/Fallout.Build/Telemetry/Telemetry.Events.cs @@ -3,6 +3,7 @@ using System.Linq; using Fallout.Common.Utilities; using Fallout.Common.Utilities.Collections; +using Fallout.Domain.Execution; using Serilog; namespace Fallout.Common.Execution; diff --git a/src/Fallout.Core/Execution/ExecutionStatus.cs b/src/Fallout.Domain/Execution/ExecutionStatus.cs similarity index 80% rename from src/Fallout.Core/Execution/ExecutionStatus.cs rename to src/Fallout.Domain/Execution/ExecutionStatus.cs index d48c5a0e9..0de85014e 100644 --- a/src/Fallout.Core/Execution/ExecutionStatus.cs +++ b/src/Fallout.Domain/Execution/ExecutionStatus.cs @@ -1,4 +1,4 @@ -namespace Fallout.Common.Execution; +namespace Fallout.Domain.Execution; public enum ExecutionStatus { diff --git a/src/Fallout.Core/Execution/ITargetModel.cs b/src/Fallout.Domain/Execution/ITargetModel.cs similarity index 97% rename from src/Fallout.Core/Execution/ITargetModel.cs rename to src/Fallout.Domain/Execution/ITargetModel.cs index de86178a9..29685f6ae 100644 --- a/src/Fallout.Core/Execution/ITargetModel.cs +++ b/src/Fallout.Domain/Execution/ITargetModel.cs @@ -1,6 +1,6 @@ using System.Collections.Generic; -namespace Fallout.Common.Execution; +namespace Fallout.Domain.Execution; /// /// Read-only projection of a build target: its identity, status, and dependency shape. diff --git a/src/Fallout.Core/Fallout.Core.csproj b/src/Fallout.Domain/Fallout.Domain.csproj similarity index 100% rename from src/Fallout.Core/Fallout.Core.csproj rename to src/Fallout.Domain/Fallout.Domain.csproj diff --git a/src/Fallout.Core/Planning/Graph/StronglyConnectedComponent.cs b/src/Fallout.Domain/Planning/Graph/StronglyConnectedComponent.cs similarity index 94% rename from src/Fallout.Core/Planning/Graph/StronglyConnectedComponent.cs rename to src/Fallout.Domain/Planning/Graph/StronglyConnectedComponent.cs index 81d171e0c..1c7322cf2 100644 --- a/src/Fallout.Core/Planning/Graph/StronglyConnectedComponent.cs +++ b/src/Fallout.Domain/Planning/Graph/StronglyConnectedComponent.cs @@ -1,7 +1,7 @@ using System.Collections; using System.Collections.Generic; -namespace Fallout.Core.Planning; +namespace Fallout.Domain.Planning; internal class StronglyConnectedComponent : IEnumerable> { diff --git a/src/Fallout.Core/Planning/Graph/StronglyConnectedComponentFinder.cs b/src/Fallout.Domain/Planning/Graph/StronglyConnectedComponentFinder.cs similarity index 97% rename from src/Fallout.Core/Planning/Graph/StronglyConnectedComponentFinder.cs rename to src/Fallout.Domain/Planning/Graph/StronglyConnectedComponentFinder.cs index 11489cf60..9a7d9a538 100644 --- a/src/Fallout.Core/Planning/Graph/StronglyConnectedComponentFinder.cs +++ b/src/Fallout.Domain/Planning/Graph/StronglyConnectedComponentFinder.cs @@ -1,7 +1,7 @@ using System; using System.Collections.Generic; -namespace Fallout.Core.Planning; +namespace Fallout.Domain.Planning; internal class StronglyConnectedComponentFinder { diff --git a/src/Fallout.Core/Planning/Graph/StronglyConnectedComponentList.cs b/src/Fallout.Domain/Planning/Graph/StronglyConnectedComponentList.cs similarity index 96% rename from src/Fallout.Core/Planning/Graph/StronglyConnectedComponentList.cs rename to src/Fallout.Domain/Planning/Graph/StronglyConnectedComponentList.cs index f2ea8f557..998f9458f 100644 --- a/src/Fallout.Core/Planning/Graph/StronglyConnectedComponentList.cs +++ b/src/Fallout.Domain/Planning/Graph/StronglyConnectedComponentList.cs @@ -2,7 +2,7 @@ using System.Collections.Generic; using System.Linq; -namespace Fallout.Core.Planning; +namespace Fallout.Domain.Planning; internal class StronglyConnectedComponentList : IEnumerable> { diff --git a/src/Fallout.Core/Planning/Graph/Vertex.cs b/src/Fallout.Domain/Planning/Graph/Vertex.cs similarity index 95% rename from src/Fallout.Core/Planning/Graph/Vertex.cs rename to src/Fallout.Domain/Planning/Graph/Vertex.cs index 2819a4122..1347d2ed6 100644 --- a/src/Fallout.Core/Planning/Graph/Vertex.cs +++ b/src/Fallout.Domain/Planning/Graph/Vertex.cs @@ -1,7 +1,7 @@ using System.Collections.Generic; using System.Linq; -namespace Fallout.Core.Planning; +namespace Fallout.Domain.Planning; internal class Vertex { diff --git a/src/Fallout.Core/Planning/TopoSort.cs b/src/Fallout.Domain/Planning/TopoSort.cs similarity index 99% rename from src/Fallout.Core/Planning/TopoSort.cs rename to src/Fallout.Domain/Planning/TopoSort.cs index eb0299f2f..d52abe3e2 100644 --- a/src/Fallout.Core/Planning/TopoSort.cs +++ b/src/Fallout.Domain/Planning/TopoSort.cs @@ -2,7 +2,7 @@ using System.Collections.Generic; using System.Linq; -namespace Fallout.Core.Planning; +namespace Fallout.Domain.Planning; /// /// The outcome of a topological ordering: the ordered nodes, any dependency cycles that were diff --git a/src/Fallout.Core/README.md b/src/Fallout.Domain/README.md similarity index 100% rename from src/Fallout.Core/README.md rename to src/Fallout.Domain/README.md diff --git a/tests/Fallout.Build.Tests/BuildExecutorTest.cs b/tests/Fallout.Build.Tests/BuildExecutorTest.cs index 98728a01b..d4ed53e86 100644 --- a/tests/Fallout.Build.Tests/BuildExecutorTest.cs +++ b/tests/Fallout.Build.Tests/BuildExecutorTest.cs @@ -2,6 +2,7 @@ using System.Linq; using FluentAssertions; using Fallout.Common.Execution; +using Fallout.Domain.Execution; using Fallout.Common.Utilities.Collections; using Xunit; diff --git a/tests/Fallout.Core.Tests/ArchitectureFitnessTests.cs b/tests/Fallout.Domain.Tests/ArchitectureFitnessTests.cs similarity index 54% rename from tests/Fallout.Core.Tests/ArchitectureFitnessTests.cs rename to tests/Fallout.Domain.Tests/ArchitectureFitnessTests.cs index d4aed0960..3bf3c9ffc 100644 --- a/tests/Fallout.Core.Tests/ArchitectureFitnessTests.cs +++ b/tests/Fallout.Domain.Tests/ArchitectureFitnessTests.cs @@ -1,23 +1,23 @@ using System.Linq; using System.Reflection; using FluentAssertions; -using Fallout.Core.Planning; +using Fallout.Domain.Planning; using NetArchTest.Rules; using Xunit; -namespace Fallout.Core.Tests; +namespace Fallout.Domain.Tests; /// -/// The acceptance criterion for issue #88: Fallout.Core is the pure reactor core. It depends on -/// nothing in the repo and never touches I/O, processes, the console, or logging. The broader -/// architecture-fitness suite lands in #95; these two tests guard the Core invariant specifically. +/// Fallout.Domain is the innermost onion ring (ADR-0006; originally issue #88's "reactor core"): +/// pure domain types and graph algorithms that touch no I/O and depend on nothing else in the repo. +/// These tests guard that invariant — the dependency rule every outer ring builds on. /// public class ArchitectureFitnessTests { - private static readonly Assembly CoreAssembly = typeof(TopoSort).Assembly; + private static readonly Assembly DomainAssembly = typeof(TopoSort).Assembly; [Fact] - public void Core_has_no_io_process_console_or_logging_dependency() + public void Domain_has_no_io_process_console_or_logging_dependency() { // Scope to our own Fallout.* types only. This excludes build-tool noise injected into the // assembly that we don't author and can't keep pure: the generated `ThisAssembly` @@ -25,7 +25,7 @@ public void Core_has_no_io_process_console_or_logging_dependency() // (coverage instrumentation under `./build.ps1 Test`, which legitimately touches System.IO). // Precise tokens (e.g. "System.Diagnostics.Process") rather than the broad "System.Diagnostics" // namespace also avoid NetArchTest false-positives on generic types. - var result = Types.InAssembly(CoreAssembly) + var result = Types.InAssembly(DomainAssembly) .That().ResideInNamespaceStartingWith("Fallout") .Should() .NotHaveDependencyOnAny( @@ -36,26 +36,31 @@ public void Core_has_no_io_process_console_or_logging_dependency() .GetResult(); result.IsSuccessful.Should().BeTrue( - because: "Fallout.Core must stay pure; offending types: " + FailingTypes(result)); + because: "Fallout.Domain must stay pure; offending types: " + FailingTypes(result)); } [Fact] - public void Core_does_not_depend_on_higher_fallout_layers() + public void Domain_does_not_depend_on_any_outer_ring() { - var result = Types.InAssembly(CoreAssembly) + // ADR-0006 onion rule: the innermost ring references no outer ring — and crucially none of the + // dissolving `Fallout.Common.*` catch-all (the whole point of the realignment) nor the + // Application/Infrastructure rings to come. + var result = Types.InAssembly(DomainAssembly) .That().ResideInNamespaceStartingWith("Fallout") .Should() .NotHaveDependencyOnAny( + "Fallout.Common", + "Fallout.Application", + "Fallout.Infrastructure", "Fallout.Build", - "Fallout.Common.Tooling", - "Fallout.Common.Utilities", - "Fallout.ProjectModel", + "Fallout.Components", "Fallout.Tooling", - "Fallout.Utilities") + "Fallout.Utilities", + "Fallout.ProjectModel") .GetResult(); result.IsSuccessful.Should().BeTrue( - because: "Fallout.Core sits at the bottom and must reference no other Fallout project; " + + because: "Fallout.Domain is the innermost ring and must reference no other Fallout project; " + "offending types: " + FailingTypes(result)); } diff --git a/tests/Fallout.Core.Tests/Fallout.Core.Tests.csproj b/tests/Fallout.Domain.Tests/Fallout.Domain.Tests.csproj similarity index 74% rename from tests/Fallout.Core.Tests/Fallout.Core.Tests.csproj rename to tests/Fallout.Domain.Tests/Fallout.Domain.Tests.csproj index fdb7ca21c..b700dfec4 100644 --- a/tests/Fallout.Core.Tests/Fallout.Core.Tests.csproj +++ b/tests/Fallout.Domain.Tests/Fallout.Domain.Tests.csproj @@ -5,7 +5,7 @@ - + diff --git a/tests/Fallout.Core.Tests/TopoSortTests.cs b/tests/Fallout.Domain.Tests/TopoSortTests.cs similarity index 97% rename from tests/Fallout.Core.Tests/TopoSortTests.cs rename to tests/Fallout.Domain.Tests/TopoSortTests.cs index bbc93cd96..b281d569c 100644 --- a/tests/Fallout.Core.Tests/TopoSortTests.cs +++ b/tests/Fallout.Domain.Tests/TopoSortTests.cs @@ -1,10 +1,10 @@ using System.Collections.Generic; using System.Linq; using FluentAssertions; -using Fallout.Core.Planning; +using Fallout.Domain.Planning; using Xunit; -namespace Fallout.Core.Tests; +namespace Fallout.Domain.Tests; public class TopoSortTests { diff --git a/tests/Fallout.SourceGenerators.Tests/StronglyTypedSolutionGeneratorTest.Test#Solution.g.verified.cs b/tests/Fallout.SourceGenerators.Tests/StronglyTypedSolutionGeneratorTest.Test#Solution.g.verified.cs index 2dec22605..18855dfd8 100644 --- a/tests/Fallout.SourceGenerators.Tests/StronglyTypedSolutionGeneratorTest.Test#Solution.g.verified.cs +++ b/tests/Fallout.SourceGenerators.Tests/StronglyTypedSolutionGeneratorTest.Test#Solution.g.verified.cs @@ -20,8 +20,8 @@ internal class Solution(SolutionModel model, AbsolutePath path) : Fallout.Soluti public Fallout.Solutions.Project Fallout_Components_Tests => this.GetProject("Fallout.Components.Tests"); public Fallout.Solutions.Project Fallout_Consumer_Local => this.GetProject("Fallout.Consumer.Local"); public Fallout.Solutions.Project Fallout_Consumer_NuGet => this.GetProject("Fallout.Consumer.NuGet"); - public Fallout.Solutions.Project Fallout_Core => this.GetProject("Fallout.Core"); - public Fallout.Solutions.Project Fallout_Core_Tests => this.GetProject("Fallout.Core.Tests"); + public Fallout.Solutions.Project Fallout_Domain => this.GetProject("Fallout.Domain"); + public Fallout.Solutions.Project Fallout_Domain_Tests => this.GetProject("Fallout.Domain.Tests"); public Fallout.Solutions.Project Fallout_Migrate => this.GetProject("Fallout.Migrate"); public Fallout.Solutions.Project Fallout_Migrate_Analyzers => this.GetProject("Fallout.Migrate.Analyzers"); public Fallout.Solutions.Project Fallout_Migrate_Analyzers_Tests => this.GetProject("Fallout.Migrate.Analyzers.Tests"); From 259c058d60625b866f96e24b9fe254346cfa0fcf Mon Sep 17 00:00:00 2001 From: Chrison Simtian Date: Sun, 31 May 2026 21:20:55 +1200 Subject: [PATCH 5/6] =?UTF-8?q?docs:=20spike=200002=20verdict=20=E2=80=94?= =?UTF-8?q?=20Domain=20ring=20extracted,=20mechanics=20proven?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Opus 4.8 --- docs/spikes/0002-onion-domain-ring.md | 22 +++++++++++++++++++++- 1 file changed, 21 insertions(+), 1 deletion(-) diff --git a/docs/spikes/0002-onion-domain-ring.md b/docs/spikes/0002-onion-domain-ring.md index 1d483278a..336dde2dc 100644 --- a/docs/spikes/0002-onion-domain-ring.md +++ b/docs/spikes/0002-onion-domain-ring.md @@ -1,6 +1,6 @@ # Spike 0002 — Onion realignment: prove the Domain ring -- **Status:** Planned +- **Status:** Done (2026-05-31) — see [Verdict](#verdict) - **Date:** 2026-05-31 - **Decision record:** [ADR-0006](../adr/0006-onion-layering-and-namespace-realignment.md) - **Channel:** `experimental` (spike branch `spike/onion-structure`) @@ -47,3 +47,23 @@ ## Exit Set **Status: Done**, append the verdict, and feed it back into ADR-0006 (confirm/adjust the layer mapping, then promote `Proposed` → `Accepted`) before starting the Application ring. + +## Verdict + +**Hypothesis confirmed.** The move → reference-fixup → fitness loop holds. `Fallout.Domain` exists, no domain type remains under `Fallout.Common.*`, the solution builds, and the **full test suite passes** (every project, including the `Nuke.*` shim tests). + +**What landed (commit `refactor(arch)!: extract Fallout.Domain ring`):** +- `Fallout.Core` → `Fallout.Domain` (project + test project, via `git mv`); refs in `Fallout.Build.csproj`, the test csproj, and `fallout.slnx` updated. +- The two mis-namespaced execution types (`ITargetModel`, `ExecutionStatus`) moved out of `Fallout.Common.Execution` → `Fallout.Domain.Execution`; `Fallout.Core.Planning` → `Fallout.Domain.Planning`. +- 8 consumers in `Fallout.Build`/tests gained `using Fallout.Domain.Execution;`; the intra-repo `ExecutionStatus` **type-forwarder re-pointed**. +- Fitness test **strengthened**: Domain must depend on no outer ring *including `Fallout.Common`* (the realignment goal). Passes; proven red on an injected dependency, then reverted. + +**Findings that shape the bigger rings:** +1. **Head start that won't repeat.** `Fallout.Core` was already pure with an existing fitness test (issue #88's seed). The Domain ring was therefore a *rename*, not a purity fight. The Application/Infrastructure rings start from the `Fallout.Common.*` tangle — expect real work, not just renames. +2. **Shim breakage is SILENT, not fatal — good news for the deferred strategy.** Moving types out of `Fallout.Common.*` doesn't fail the shim build; the `TransitionShimGenerator` simply stops mirroring them into `Nuke.*` (it only maps the `Fallout.Common` prefix). The solution built and the shim tests passed. So the rings won't be *blocked* by shim breakage — the `Nuke.*` surface just quietly shrinks, to be rebuilt by the deferred migration phase. **Caveat:** this was 2 types (one an enum the generator skips anyway). The **Application ring** moves the user-facing API *en masse* — the silent surface reduction there will be large; still expected to be build-clean. +3. **Blast radius beyond namespaces is real but mechanical.** Two non-obvious things needed updating: an **intra-repo type-forwarder** and a **Verify snapshot** (the solution-generator emits project names, so renaming projects churns it). The Application/Infrastructure rings will hit *many* more consumers and likely more snapshots — same loop, larger N. +4. **Splitting a shared namespace costs double-usings.** Because `Fallout.Common.Execution` keeps its orchestration types (staying → future `Fallout.Application.Execution`) while the model types left, consumers need *both* usings transiently. The Application ring (whole-namespace move) should be more uniform but far larger — **worth a bulk using-rewrite tool** (or lean on the `Fallout.Migrate` codefix machinery). + +**Safe to scale to the Application ring** — the mechanics are proven. That ring is the big one (the user-facing `FalloutBuild`/`Target`/`[Parameter]` rename); recommend it as its own PR, with a bulk using-rewriter prepared first. + +*Residual nit:* `src/Fallout.Domain/README.md` may still contain `Fallout.Core` prose — cosmetic, sweep during the Application ring. From 498d150bdfe3e8afea2ec3214c53227bb29f1d9d Mon Sep 17 00:00:00 2001 From: Chrison Simtian Date: Sun, 31 May 2026 21:44:57 +1200 Subject: [PATCH 6/6] docs: correct realignment target to the (unreleased) 2026 major; add CHANGELOG MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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 --- CHANGELOG.md | 3 +++ docs/adr/0006-onion-layering-and-namespace-realignment.md | 8 ++++---- 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 49a482d1a..c94dfb169 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,9 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0. ### Breaking changes +- **Onion layering — namespace realignment begun; `Fallout.Core` → `Fallout.Domain`** ([ADR-0006](docs/adr/0006-onion-layering-and-namespace-realignment.md)). First ring of a project-wide realignment to explicit onion layers (`Fallout.Domain` / `Fallout.Application` / `Fallout.Infrastructure`, `Fallout.Cli` composition root) with `namespace = project = layer`, dissolving the `Fallout.Common.*` catch-all. This entry covers **step 1 (the Domain ring)**; the Application/Infrastructure/tooling-port rings land on `experimental` as separate PRs (any that miss the 2026 cut roll to 2027). + - **Namespaces**: `Fallout.Core.Planning` → `Fallout.Domain.Planning`; `ITargetModel` and `ExecutionStatus` move out of `Fallout.Common.Execution` → `Fallout.Domain.Execution`. **Package/assembly**: `Fallout.Core` → `Fallout.Domain`. Replace the corresponding `using` directives and any `PackageReference`/`ProjectReference`. + - **Migration / shim**: the `Nuke.*` transition shims silently stop re-exporting the two moved types (the generator only mirrors the `Fallout.Common.*` prefix). A fresh migration/shim strategy is designed once the realignment completes (ADR-0006 defers it deliberately); native `Fallout.*` consumers rewrite the two `using`s above. - **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. diff --git a/docs/adr/0006-onion-layering-and-namespace-realignment.md b/docs/adr/0006-onion-layering-and-namespace-realignment.md index c0f01f7f4..f4553cd68 100644 --- a/docs/adr/0006-onion-layering-and-namespace-realignment.md +++ b/docs/adr/0006-onion-layering-and-namespace-realignment.md @@ -19,7 +19,7 @@ The project + namespace structure is inherited verbatim from NUKE: the rebrand ( `docs/rebrand-plan.md` explicitly **defers** "realigning project ↔ namespace" to "a future major version after the shim packages have sunset," citing the type-forwarding bridge as the blocker. This ADR **amends that deferral** on two grounds: - **The bridge is not a blocker.** The rebrand-plan deferred this work *because* it assumed a rigid `[TypeForwardedTo]` bridge. The actual machinery is the `TransitionShimGenerator` (`ShimAllPublicTypesUnder(from, to)`), a *prefix-remappable* subclass generator — so restructuring `Fallout.*` doesn't orphan the `Nuke.*` surface in principle. We nonetheless **defer the migration strategy wholesale** (see Decision §"Migration & shim strategy") rather than re-point ring-by-ring; the point here is only that the stated blocker doesn't bind. -- **ADR-0004 gives a clean home.** Namespace realignment is breaking → it lands on `experimental` and is **batched to the `2027.0.0` yearly major**. The work happens in 2026; native `Fallout.*` consumers are carried by re-pointed shims + the `Fallout.Migrate` codefix. Old `Fallout.*` namespaces are deleted at the cut, not dragged. +- **ADR-0004 gives a clean home.** Namespace realignment is breaking → it lands on `experimental` and is **batched to the next yearly major cut**. The `2026` major has **not cut yet** (no calendar-version GA tag exists; CHANGELOG `[Unreleased] — 2026.0` is still accumulating breaking changes), so this work can ride the **2026** cut and ship this year. Rings that aren't done before `2026.0.0` cuts roll to `2027`. Either way native `Fallout.*` consumers are carried by the deferred migration phase; old `Fallout.*` namespaces are deleted at the cut, not dragged. (The rebrand-plan is a transient maintainer-owned doc; this is a deliberate, recorded reversal of one of its deferrals, not a silent contradiction.) @@ -41,7 +41,7 @@ The project + namespace structure is inherited verbatim from NUKE: the rebrand ( **Rules:** 1. **`namespace == project == layer`.** No project declares a namespace rooted outside its layer. `Fallout.Common` is **dissolved**. 2. **Onion dependency rule, fitness-enforced.** Domain references no other Fallout assembly; Application references only Domain; Infrastructure references Application/Domain; only the Cli composition root references Infrastructure. One architecture-test per ring, added as each ring lands (extends the ADR-0005 boundary-test pattern). -3. **Breaking → `experimental` → `2027.0.0`** (ADR-0004). A breaking PR targets `experimental` only and carries `target/2027` + `breaking-change` + a `CHANGELOG.md` migration entry. +3. **Breaking → `experimental` → the next yearly major** (ADR-0004). The `2026` major is still unreleased, so rings ride the **2026** cut (`target/2026`) until it closes; anything after rolls to `2027`. A breaking PR targets `experimental` only and carries `target/` + `breaking-change` + a `CHANGELOG.md` entry under that major. 4. **Ring-by-ring migration**, inner to outer — each ring is its own PR on `experimental`. The spike (0002) proves the mechanics on the Domain ring before the larger rings. 5. **Migration/shim strategy is deferred wholesale** — see below. @@ -49,7 +49,7 @@ The project + namespace structure is inherited verbatim from NUKE: the rebrand ( The `Nuke.*` transition shims (and any native-`Fallout.*` migration aid) are **explicitly out of scope for the rearchitecture rings.** Rationale: there's no point re-pointing the existing `TransitionShimGenerator` ring-by-ring toward a target that's still moving. Instead — once the final layered shape has settled — we design a **fresh migration/shim strategy that fits whatever we ended up with**, as its own phase and its own ADR. The existing bridge is re-pointable (it's a prefix-remappable subclass generator, not raw `[TypeForwardedTo]`), so this deferral costs us no future optionality; it's a sequencing choice, not a capability loss. -Consequence during the work: on `experimental`, `Nuke.*` shim parity is **not maintained** while the rings land. That's acceptable — `experimental` is the unstable lane, and `2027.0.0` is the only deadline that matters; the new migration story is built before the cut. `Fallout.Migrate` likewise gets revisited then, not incrementally. +Consequence during the work: on `experimental`, `Nuke.*` shim parity is **not maintained** while the rings land. That's acceptable — `experimental` is the unstable lane, and the only deadline that matters is the major cut the rings target; the new migration story is built before that cut. `Fallout.Migrate` likewise gets revisited then, not incrementally. ### Sequence (each step a PR on `experimental`, with its own ring fitness test) @@ -76,7 +76,7 @@ Migration/shim strategy is redesigned after step 5, before the cut (deferred, ab ### Negative - **Large, breaking, multi-PR.** Touches nearly every file's `namespace`/`using`. Mitigated by ring-by-ring sequencing, re-pointed shims, the migrate codefix, and batching to one yearly major. -- **Consumer churn** for native `Fallout.*` users, and a window on `experimental` where `Nuke.*` shim parity lapses. Both are carried by the deferred migration phase (a fresh strategy + CHANGELOG migration guide), built before the `2027.0.0` cut — not during the rings. +- **Consumer churn** for native `Fallout.*` users, and a window on `experimental` where `Nuke.*` shim parity lapses. Both are carried by the deferred migration phase (a fresh strategy + CHANGELOG migration guide), built before the target major cut — not during the rings. ## Alternatives considered