From b3277f9c9fc70372eb77040e2ee56611e40fbfd1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ricardo=20Gro=C3=9F?= Date: Thu, 29 Oct 2026 20:30:57 +0100 Subject: [PATCH 1/4] docs: decision 120, a net11.0 target keeps every build and 3.0.0 stays as planned A net11.0 asset joins a 2.x minor only with the net10.0 group's dependency floors (a higher floor is NU1605 for an application that holds the lower version, measured on SDK 11 RC1), a superset surface that needs no C# 15 to call, the same generated members on every target and LangVersion 14 until an implementation needs 15. The SDK 11 spike (#170) comes first. 3.0.0 stays C# 15 unions and the net10.0 drop, timed to .NET 12 LTS; refines decision 44. Closes #169 Co-Authored-By: Claude Sonnet 5.5 --- ...ery-build-and-csharp-15-waits-for-3-0-0.md | 75 +++++++++++++++++++ design/adr/README.md | 3 +- design/roadmap.md | 15 +++- 3 files changed, 89 insertions(+), 4 deletions(-) create mode 100644 design/adr/0120-a-net11-0-target-keeps-every-build-and-csharp-15-waits-for-3-0-0.md diff --git a/design/adr/0120-a-net11-0-target-keeps-every-build-and-csharp-15-waits-for-3-0-0.md b/design/adr/0120-a-net11-0-target-keeps-every-build-and-csharp-15-waits-for-3-0-0.md new file mode 100644 index 0000000..d3ce36c --- /dev/null +++ b/design/adr/0120-a-net11-0-target-keeps-every-build-and-csharp-15-waits-for-3-0-0.md @@ -0,0 +1,75 @@ +# 0120. A `net11.0` target joins a 2.x minor only if it keeps every build; C# 15 and the `net10.0` drop stay 3.0.0 + +Date: 2026-10-29 +Status: accepted + +Context: 2026-10-29, issue #169; refines [decision 44](0044-2-0-0-net10-0-metalama.md). Decision 44 says a +`net11.0` target is additive and joins a 2.x minor when a feature needs it, and that unions and the `net10.0` +drop are 3.0.0, timed to .NET 12 LTS. The maintainer asked what making 3.0.0 the .NET 11 release would +change, and decided, in these words: "stick to the original plans regarding 3.0.0, add details to new 2.x +minors and .NET 11 related specifics". .NET 11 is a release candidate (SDK 11.0.100-rc.1 measured here, RC2 +named in [KB-046](../knowledge/KB-046-csharp-15-unions-ship-with-the-net-11-sdk.md)), with general +availability expected in November 2026. + +## Decision + +**3.0.0 stays as decision 44 and the roadmap have it:** C# 15 unions in the public surface and the `net10.0` +drop, timed to .NET 12 LTS (November 2027) or to the end of .NET 10's support, whichever its consumers need. +A 3.0.0 for .NET 11 was considered and refused. A `net11.0`-only major strands the consumers on the .NET 10 +LTS line, supported to November 2028, which is the argument that refused a `net11.0`-only 2.0.0. And nothing +.NET 11 offers needs a break: unions in `Option` and `Result` wait for the spike the roadmap names, since the +compiler-made union types have a public `object? Value` and are serialized by System.Text.Json. The version +number names the API, never the runtime. + +**A `net11.0` asset joins a 2.x minor when a feature needs it, and only if it keeps every consumer's build.** +No patch adds a target. The next minor after the one in flight carries it; the number is not reserved here. +It holds when: + +1. **Its dependency groups keep the `net10.0` group's floors.** An application on `net11.0` may hold the lower + version of a satellite's dependency (EF Core 10 and Microsoft.AspNetCore.OpenApi 10 are `net10.0` packages, + which a `net11.0` project may reference). + NuGet picks the `net11.0` group for it, and a floor that group raises is a downgrade error, NU1605, + on update (measured below). The satellites compile against the floors they have and are *tested* against + EF Core 11 and OpenAPI 11. A feature that needs an API only EF Core 11 or OpenAPI 11 has waits for 3.0.0, + unless it lives in a package no consumer has installed yet. Raising a floor is a release decision, and in + a 2.x minor it is refused. +2. **Its public surface is a superset of `net10.0`'s, and nothing in it needs C# 15 to call.** A member only + `net11.0` has is available on that target alone, and the changelog says so. No union type and no `closed` + type appears in a public signature before 3.0.0. +3. **Generated members are the same on every target.** The generators run in the consumer's compiler whatever + the asset, and a value object's generated surface is the contract. A member that exists on one target only + would make a consumer that multi-targets see two value objects under one name; any member the generators + introduce on `net11.0` is introduced on `net10.0` too, or not at all. +4. **`LangVersion` stays 14, and becomes per target only when the `net11.0` code uses a C# 15 feature inside + an implementation.** The consumer needs nothing from the compiler the assembly was built with. Generated + code compiles under C# 14 and under the C# 15 that SDK 11 defaults a `net11.0` consumer to. +5. **Each target is run, not assumed:** the tests, the Native AOT smoke run and package validation per target, + and the smoke test's consumer on `net11.0` that holds the lower satellite versions. + +**The SDK comes first.** `global.json` pins SDK 10.0.4xx, because a different Roslyn band can change what +Metalama and the analyzers emit. A `net11.0` target needs SDK 11, which changes the compiler under the `net10.0` +assets too, and a consumer on SDK 11 meets it already with the 2.x `net10.0` asset. Before any `net11.0` +target, a spike with no release attached (#170) measures on an SDK 11 compiler: the build and tests, the packed +`net10.0` assets unchanged, a consumer on SDK 11 building and weaving, and the smoke test's SDK 11 lane. The +pin moves by a record of its own, after the SDK is generally available. + +**The 2.x line after 3.0.0** is decided with 3.0.0, not here. + +## Evidence + +SDK 11.0.100-rc.1.26425.128, a throwaway package `FloorLib` 1.0.0 with a `net10.0` group requiring +`Newtonsoft.Json >= 12.0.3` and a `net11.0` group requiring `>= 13.0.3`. An application that references +`Newtonsoft.Json 12.0.3` directly and `FloorLib`: + +- on `net11.0`: `NU1605`, "Detected package downgrade: Newtonsoft.Json from 13.0.3 to 12.0.3", with the path + `App -> FloorLib 1.0.0 -> Newtonsoft.Json (>= 13.0.3)`; an error under the SDK's defaults; +- on `net10.0`: restores. + +Adding a target is therefore not additive by itself. The roadmap's "EF Core 11 and Microsoft.AspNetCore.OpenApi +11 in the satellites" is that case, and reads as the test lane in the first rule above from now on. + +## Consequences + +- The roadmap's `net11.0` item names these rules, and its 3.0.0 item is unchanged. +- Decision 44 stands; its index row says it is refined here. +- Whether Metalama 2026.1 and the analyzers hold under the SDK 11 compiler is open until #170. diff --git a/design/adr/README.md b/design/adr/README.md index 2f184f3..4d944c6 100644 --- a/design/adr/README.md +++ b/design/adr/README.md @@ -50,7 +50,7 @@ per area ([design](../plan.md)) link to the records they rest on. | [0041](0041-sensitive-value-object-declares-tostring-serilog.md) | A sensitive value object is one that declares `ToString()`; Serilog gets a satellite | 2026-10-11 | accepted | | [0042](0042-2-0-0-next-release-bundles.md) | 2.0.0 is the next release, and it bundles the 1.x candidates | 2026-10-12 | accepted | | [0043](0043-exceptions-type-argument-constraints-constructor-guarantee.md) | Exceptions, type-argument constraints, a constructor guarantee and the text guards join 2.0.0 | 2026-10-13 | accepted | -| [0044](0044-2-0-0-net10-0-metalama.md) | 2.0.0 is `net10.0` on Metalama 2026.1; the version number names the API, never the runtime | 2026-10-13 | accepted | +| [0044](0044-2-0-0-net10-0-metalama.md) | 2.0.0 is `net10.0` on Metalama 2026.1; the version number names the API, never the runtime | 2026-10-13 | accepted; refined by [0120](0120-a-net11-0-target-keeps-every-build-and-csharp-15-waits-for-3-0-0.md) | | [0045](0045-topic-pages-docs-compiled-like-readmes.md) | Topic pages in `docs/`, compiled like the READMEs; the plan in `design/` | 2026-10-13 | accepted | | [0046](0046-timeout-caught-result-catch-error.md) | A timeout caught by `Result.Catch` is the error | 2026-10-13 | accepted | | [0047](0047-firstornone-query-runs-query.md) | `FirstOrNone` on a query runs in the query | 2026-10-13 | accepted | @@ -126,3 +126,4 @@ per area ([design](../plan.md)) link to the records they rest on. | [0117](0117-a-seed-is-caught-against-a-passing-baseline.md) | `oracles seeds` credits a seed only when its oracle passes without it, in a baseline run with the same build and filter, and fails with it in a test of its own; a run that never reached the tests (a fixture xunit could not set up, an error counted outside any test, a crashed host, no TRX, every test skipped) or a failing baseline is `ERROR`, exit 4, never `CAUGHT` | 2026-10-29 | accepted | | [0118](0118-an-error-may-learn-a-form-only-new-api-can-write.md) | An Error may report, in a minor version, a form that only an API new in that version can write, since no build that worked before contains it: CMTK0003's dropped assertion of `CodoMetis.TypeKit.Testing` on a `Task` or `ValueTask` in 2.3.0, its `ConfigureAwait`, `WaitAsync` and branch forms (#152) included, known by the package's assembly; the forms freeze at the tag; refines decision 34; one exception the maintainer took into 2.3.0, a dropped `Task.WhenAny` of tasks of results (#159) | 2026-10-29 | accepted; widened by [0119](0119-a-gap-found-before-the-tag-ships-in-that-release.md) | | [0119](0119-a-gap-found-before-the-tag-ships-in-that-release.md) | A form an Error misses, found before its version is tagged, is reported in that version when it loses what the rule protects in every build, though code built on the previous minor can contain it: 2.3.0's CMTK0003 on a `Task.WhenAny` of results (#159), `Wait()` on a branch (#160), an array in `Task.WaitAll`/`WaitAny` (#161), a dropped `Task.WhenEach` (#162), and a `WhenAny` of held tasks silent; the rule stops at the tag, after which decision 34 holds; refines 34 and 118 | 2026-10-29 | accepted | +| [0120](0120-a-net11-0-target-keeps-every-build-and-csharp-15-waits-for-3-0-0.md) | A `net11.0` target joins a 2.x minor only if it keeps every consumer's build: the `net10.0` group's dependency floors (a higher floor on `net11.0` is NU1605 for an application that holds the lower version, measured on SDK 11 RC1), a superset surface that needs no C# 15 to call, the same generated members on every target, `LangVersion` 14 until an implementation needs 15, each target run; the SDK 11 spike comes first (#170); 3.0.0 stays C# 15 unions and the `net10.0` drop, timed to .NET 12 LTS, and .NET 11 is no reason for a major; refines decision 44 | 2026-10-29 | accepted | diff --git a/design/roadmap.md b/design/roadmap.md index cb43946..737773e 100644 --- a/design/roadmap.md +++ b/design/roadmap.md @@ -62,8 +62,14 @@ Small, each closable in a minor version. Found by the reviews named. [decision 4](adr/0004-metalama-2026-1-x-latest-stable.md)): a minor if a consumer's own aspects beside the packed generators still build and weave, measured as the fabric spike measured 2026.1 against the 2027.0 preview, and a major otherwise; `[Durable]` returns to the `_value` placeholder with it. -- A `net11.0` target beside `net10.0`, in a 2.x minor when a feature needs it ([decision 44](adr/0044-2-0-0-net10-0-metalama.md)): EF Core 11 - and Microsoft.AspNetCore.OpenApi 11 in the satellites, and a Native AOT smoke run per target. +- A `net11.0` target beside `net10.0`, in a 2.x minor when a feature needs it ([decision 44](adr/0044-2-0-0-net10-0-metalama.md)), + on the terms of [decision 120](adr/0120-a-net11-0-target-keeps-every-build-and-csharp-15-waits-for-3-0-0.md): the + `net10.0` group's dependency floors kept, since a higher floor on `net11.0` fails an application that holds the + lower version (NU1605); the satellites tested against EF Core 11 and Microsoft.AspNetCore.OpenApi 11, not + compiled against them; the same generated members on every target; `LangVersion` 14 until an implementation + needs C# 15; the tests, a Native AOT smoke run and package validation per target. First the spike on an SDK 11 + compiler, no release attached (#170): `global.json` pins SDK 10.0.4xx because the Roslyn band changes what + Metalama emits, and the SDK is generally available in November 2026. ## Blocked upstream @@ -84,7 +90,10 @@ Small, each closable in a minor version. Found by the reviews named. - CMTK1023, a base record's equality that the generated one does not call, becomes an Error ([decision 75](adr/0075-equality-over-base-record-generated.md)). - C# 15 unions and the `net10.0` drop, timed to .NET 12 LTS (November 2027) or to the end of .NET - 10's life, whichever its consumers need ([decision 44](adr/0044-2-0-0-net10-0-metalama.md)). The compiler-made union types have a public + 10's life, whichever its consumers need ([decision 44](adr/0044-2-0-0-net10-0-metalama.md), kept for .NET 11 + by [decision 120](adr/0120-a-net11-0-target-keeps-every-build-and-csharp-15-waits-for-3-0-0.md): .NET 11 is not a + reason for a major, and a floor an update would raise, a public C# 15 form or a member on one target only is). + The compiler-made union types have a public `object? Value` and are serialized by System.Text.Json, so `Option` and `Result` take part in their pattern matching only after a spike shows neither returns, on a branch of its own with no release attached. From a7343f7f30f8d8ed34da61c3947344b268048304 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ricardo=20Gro=C3=9F?= Date: Thu, 29 Oct 2026 20:38:34 +0100 Subject: [PATCH 2/4] docs: decision 120 lets a net11.0-only union join a 2.x minor A union new on net11.0 is an addition, not a second contract: measured on SDK 11 RC1, a net10.0 consumer sees nothing, a LangVersion 14 net11.0 consumer who holds the value builds, and one who matches on it gets CS9327. 3.0.0 keeps the net10.0 drop and the change of an existing contract. The record is renamed to say so; the dependency-floor rule is unchanged. Refs #169 Co-Authored-By: Claude Sonnet 5.5 --- ...ery-build-and-csharp-15-waits-for-3-0-0.md | 75 ------------- ...et-keeps-every-build-and-may-add-unions.md | 106 ++++++++++++++++++ design/adr/README.md | 4 +- design/roadmap.md | 21 ++-- 4 files changed, 119 insertions(+), 87 deletions(-) delete mode 100644 design/adr/0120-a-net11-0-target-keeps-every-build-and-csharp-15-waits-for-3-0-0.md create mode 100644 design/adr/0120-a-net11-0-target-keeps-every-build-and-may-add-unions.md diff --git a/design/adr/0120-a-net11-0-target-keeps-every-build-and-csharp-15-waits-for-3-0-0.md b/design/adr/0120-a-net11-0-target-keeps-every-build-and-csharp-15-waits-for-3-0-0.md deleted file mode 100644 index d3ce36c..0000000 --- a/design/adr/0120-a-net11-0-target-keeps-every-build-and-csharp-15-waits-for-3-0-0.md +++ /dev/null @@ -1,75 +0,0 @@ -# 0120. A `net11.0` target joins a 2.x minor only if it keeps every build; C# 15 and the `net10.0` drop stay 3.0.0 - -Date: 2026-10-29 -Status: accepted - -Context: 2026-10-29, issue #169; refines [decision 44](0044-2-0-0-net10-0-metalama.md). Decision 44 says a -`net11.0` target is additive and joins a 2.x minor when a feature needs it, and that unions and the `net10.0` -drop are 3.0.0, timed to .NET 12 LTS. The maintainer asked what making 3.0.0 the .NET 11 release would -change, and decided, in these words: "stick to the original plans regarding 3.0.0, add details to new 2.x -minors and .NET 11 related specifics". .NET 11 is a release candidate (SDK 11.0.100-rc.1 measured here, RC2 -named in [KB-046](../knowledge/KB-046-csharp-15-unions-ship-with-the-net-11-sdk.md)), with general -availability expected in November 2026. - -## Decision - -**3.0.0 stays as decision 44 and the roadmap have it:** C# 15 unions in the public surface and the `net10.0` -drop, timed to .NET 12 LTS (November 2027) or to the end of .NET 10's support, whichever its consumers need. -A 3.0.0 for .NET 11 was considered and refused. A `net11.0`-only major strands the consumers on the .NET 10 -LTS line, supported to November 2028, which is the argument that refused a `net11.0`-only 2.0.0. And nothing -.NET 11 offers needs a break: unions in `Option` and `Result` wait for the spike the roadmap names, since the -compiler-made union types have a public `object? Value` and are serialized by System.Text.Json. The version -number names the API, never the runtime. - -**A `net11.0` asset joins a 2.x minor when a feature needs it, and only if it keeps every consumer's build.** -No patch adds a target. The next minor after the one in flight carries it; the number is not reserved here. -It holds when: - -1. **Its dependency groups keep the `net10.0` group's floors.** An application on `net11.0` may hold the lower - version of a satellite's dependency (EF Core 10 and Microsoft.AspNetCore.OpenApi 10 are `net10.0` packages, - which a `net11.0` project may reference). - NuGet picks the `net11.0` group for it, and a floor that group raises is a downgrade error, NU1605, - on update (measured below). The satellites compile against the floors they have and are *tested* against - EF Core 11 and OpenAPI 11. A feature that needs an API only EF Core 11 or OpenAPI 11 has waits for 3.0.0, - unless it lives in a package no consumer has installed yet. Raising a floor is a release decision, and in - a 2.x minor it is refused. -2. **Its public surface is a superset of `net10.0`'s, and nothing in it needs C# 15 to call.** A member only - `net11.0` has is available on that target alone, and the changelog says so. No union type and no `closed` - type appears in a public signature before 3.0.0. -3. **Generated members are the same on every target.** The generators run in the consumer's compiler whatever - the asset, and a value object's generated surface is the contract. A member that exists on one target only - would make a consumer that multi-targets see two value objects under one name; any member the generators - introduce on `net11.0` is introduced on `net10.0` too, or not at all. -4. **`LangVersion` stays 14, and becomes per target only when the `net11.0` code uses a C# 15 feature inside - an implementation.** The consumer needs nothing from the compiler the assembly was built with. Generated - code compiles under C# 14 and under the C# 15 that SDK 11 defaults a `net11.0` consumer to. -5. **Each target is run, not assumed:** the tests, the Native AOT smoke run and package validation per target, - and the smoke test's consumer on `net11.0` that holds the lower satellite versions. - -**The SDK comes first.** `global.json` pins SDK 10.0.4xx, because a different Roslyn band can change what -Metalama and the analyzers emit. A `net11.0` target needs SDK 11, which changes the compiler under the `net10.0` -assets too, and a consumer on SDK 11 meets it already with the 2.x `net10.0` asset. Before any `net11.0` -target, a spike with no release attached (#170) measures on an SDK 11 compiler: the build and tests, the packed -`net10.0` assets unchanged, a consumer on SDK 11 building and weaving, and the smoke test's SDK 11 lane. The -pin moves by a record of its own, after the SDK is generally available. - -**The 2.x line after 3.0.0** is decided with 3.0.0, not here. - -## Evidence - -SDK 11.0.100-rc.1.26425.128, a throwaway package `FloorLib` 1.0.0 with a `net10.0` group requiring -`Newtonsoft.Json >= 12.0.3` and a `net11.0` group requiring `>= 13.0.3`. An application that references -`Newtonsoft.Json 12.0.3` directly and `FloorLib`: - -- on `net11.0`: `NU1605`, "Detected package downgrade: Newtonsoft.Json from 13.0.3 to 12.0.3", with the path - `App -> FloorLib 1.0.0 -> Newtonsoft.Json (>= 13.0.3)`; an error under the SDK's defaults; -- on `net10.0`: restores. - -Adding a target is therefore not additive by itself. The roadmap's "EF Core 11 and Microsoft.AspNetCore.OpenApi -11 in the satellites" is that case, and reads as the test lane in the first rule above from now on. - -## Consequences - -- The roadmap's `net11.0` item names these rules, and its 3.0.0 item is unchanged. -- Decision 44 stands; its index row says it is refined here. -- Whether Metalama 2026.1 and the analyzers hold under the SDK 11 compiler is open until #170. diff --git a/design/adr/0120-a-net11-0-target-keeps-every-build-and-may-add-unions.md b/design/adr/0120-a-net11-0-target-keeps-every-build-and-may-add-unions.md new file mode 100644 index 0000000..cd1716e --- /dev/null +++ b/design/adr/0120-a-net11-0-target-keeps-every-build-and-may-add-unions.md @@ -0,0 +1,106 @@ +# 0120. A `net11.0` target joins a 2.x minor only if it keeps every build, and may add C# 15 unions; 3.0.0 stays the change of an existing contract + +Date: 2026-10-29 +Status: accepted + +Context: 2026-10-29, issue #169; refines [decision 44](0044-2-0-0-net10-0-metalama.md). Decision 44 says a +`net11.0` target is additive and joins a 2.x minor when a feature needs it, and that unions and the `net10.0` +drop are 3.0.0, timed to .NET 12 LTS. The maintainer asked what making 3.0.0 the .NET 11 release would +change, and decided, in these words: "stick to the original plans regarding 3.0.0, add details to new 2.x +minors and .NET 11 related specifics". .NET 11 is a release candidate (SDK 11.0.100-rc.1 measured here, RC2 +named in [KB-046](../knowledge/KB-046-csharp-15-unions-ship-with-the-net-11-sdk.md)), with general +availability expected in November 2026. + +## Decision + +**3.0.0 stays timed as decision 44 and the roadmap have it:** the `net10.0` drop, and `Option` and `Result` +becoming C# 15 unions if the spike allows it, at .NET 12 LTS (November 2027) or at the end of .NET 10's +support, whichever its consumers need. A 3.0.0 for .NET 11 was considered and refused. A `net11.0`-only major +strands the consumers on the .NET 10 LTS line, supported to November 2028, which is the argument that refused +a `net11.0`-only 2.0.0. And nothing .NET 11 offers needs a break: unions in `Option` and `Result` wait for the +spike the roadmap names, since the compiler-made union types have a public `object? Value` and are serialized +by System.Text.Json. The version number names the API, never the runtime. + +**What 3.0.0 holds is a change to an existing contract, never an addition.** Decision 44 puts "unions in the +public surface" in 3.0.0 as "two contracts under one version", which is true of a union that replaces or +retypes an existing member, and of `Option` or `Result` becoming unions: their identity, their serialization +and their pattern matching would differ between two versions of the same member. A union that is new, and +exists on `net11.0` only, is an addition like any other, and may join a 2.x minor (second rule below). A minor +that fixes bugs for every consumer and adds to `net11.0` alone is a plain 2.x minor. + +**A `net11.0` asset joins a 2.x minor when a feature needs it, and only if it keeps every consumer's build.** +No patch adds a target. The next minor after the one in flight carries it; the number is not reserved here. +It holds when: + +1. **Its dependency groups keep the `net10.0` group's floors.** An application on `net11.0` may hold the lower + version of a satellite's dependency (EF Core 10 and Microsoft.AspNetCore.OpenApi 10 are `net10.0` packages, + which a `net11.0` project may reference). + NuGet picks the `net11.0` group for it, and a floor that group raises is a downgrade error, NU1605, + on update (measured below). The satellites compile against the floors they have and are *tested* against + EF Core 11 and OpenAPI 11. A feature that needs an API only EF Core 11 or OpenAPI 11 has waits for 3.0.0, + unless it lives in a package no consumer has installed yet. Raising a floor is a release decision, and in + a 2.x minor it is refused. +2. **Its public surface is a superset of `net10.0`'s, and a shared member behaves the same on both.** A member + only `net11.0` has is available on that target alone, and the changelog says so. That includes members that + use C# 15 unions or `closed` types: the `net10.0` asset has none of them, and a `net11.0` consumer who does + not use them is untouched. One who uses them on `LangVersion` 14 gets CS9327, an error, never a different + answer (measured below). What stays out of a 2.x minor is a change to a member both targets have: its + signature, its return type, its serialization or its behaviour on one target. Two conditions on a union + member are this repository's own and are decided by the unions spike the roadmap names, not by the version: + it gives no path to the content of an `Option` or `Result` past `Match` and `TryGetValue` (a compiler-made + union has a public `object? Value`), and it is not written by System.Text.Json where `Option` and `Result` + refuse it. +3. **Generated members are the same on every target.** The generators run in the consumer's compiler whatever + the asset, and a value object's generated surface is the contract. A member that exists on one target only + would make a consumer that multi-targets see two value objects under one name; any member the generators + introduce on `net11.0` is introduced on `net10.0` too, or not at all. +4. **`LangVersion` stays 14 for `net10.0`, and is 15 for `net11.0` once its code uses a C# 15 feature**, in an + implementation or in a new member's signature. The consumer needs nothing from the compiler the assembly was + built with, except to use a union or a `closed` type, which needs C# 15 and says so (CS9327). Generated + code compiles under C# 14 and under the C# 15 that SDK 11 defaults a `net11.0` consumer to. +5. **Each target is run, not assumed:** the tests, the Native AOT smoke run and package validation per target, + and the smoke test's consumer on `net11.0` that holds the lower satellite versions. + +**The SDK comes first.** `global.json` pins SDK 10.0.4xx, because a different Roslyn band can change what +Metalama and the analyzers emit. A `net11.0` target needs SDK 11, which changes the compiler under the `net10.0` +assets too, and a consumer on SDK 11 meets it already with the 2.x `net10.0` asset. Before any `net11.0` +target, a spike with no release attached (#170) measures on an SDK 11 compiler: the build and tests, the packed +`net10.0` assets unchanged, a consumer on SDK 11 building and weaving, and the smoke test's SDK 11 lane. The +pin moves by a record of its own, after the SDK is generally available. + +**The 2.x line after 3.0.0** is decided with 3.0.0, not here. + +## Evidence + +SDK 11.0.100-rc.1.26425.128, a throwaway package `FloorLib` 1.0.0 with a `net10.0` group requiring +`Newtonsoft.Json >= 12.0.3` and a `net11.0` group requiring `>= 13.0.3`. An application that references +`Newtonsoft.Json 12.0.3` directly and `FloorLib`: + +- on `net11.0`: `NU1605`, "Detected package downgrade: Newtonsoft.Json from 13.0.3 to 12.0.3", with the path + `App -> FloorLib 1.0.0 -> Newtonsoft.Json (>= 13.0.3)`; an error under the SDK's defaults; +- on `net10.0`: restores. + +A library with `net10.0` (`LangVersion` 14) and `net11.0` assets, whose `net11.0` asset alone declares +`public union Pet(Cat, Dog)` and a method returning it, consumed by: + +- `net11.0`, C# 15 by default, with an exhaustive `switch` over the union: builds; +- `net11.0` with `LangVersion` 14, holding the union in a `var` and printing it: builds; +- `net11.0` with `LangVersion` 14, pattern matching on it: `CS9327`, "The feature 'unions' is not available in + C# 14.0. Use language version 15.0 or greater"; +- `net10.0`: builds, and sees only the shared members. + +The SDK needs no `preview` flag for any of it (RC1), as [KB-046](../knowledge/KB-046-csharp-15-unions-ship-with-the-net-11-sdk.md) +says of RC2. So a union is addable on `net11.0` in a minor. What measurement one cannot make from this repository +yet is the unions spike's: what `Option` and `Result` may offer a union without losing the guarantees above. + +Adding a target is not additive by itself, though. The roadmap's "EF Core 11 and Microsoft.AspNetCore.OpenApi +11 in the satellites" is that case, and reads as the test lane in the first rule above from now on. + +## Consequences + +- The roadmap's `net11.0` item names these rules, and its 3.0.0 item is unchanged except that it says what a + union may do in a 2.x minor. Decision 44's "unions in the public surface are 3.0.0" is narrowed here to a + union that changes an existing member; the maintainer's reading of Microsoft's compatibility guidance + (a newer target may add to an older one's surface), checked against the measurement above. +- Decision 44 stands; its index row says it is refined here. +- Whether Metalama 2026.1 and the analyzers hold under the SDK 11 compiler is open until #170. diff --git a/design/adr/README.md b/design/adr/README.md index 4d944c6..d655115 100644 --- a/design/adr/README.md +++ b/design/adr/README.md @@ -50,7 +50,7 @@ per area ([design](../plan.md)) link to the records they rest on. | [0041](0041-sensitive-value-object-declares-tostring-serilog.md) | A sensitive value object is one that declares `ToString()`; Serilog gets a satellite | 2026-10-11 | accepted | | [0042](0042-2-0-0-next-release-bundles.md) | 2.0.0 is the next release, and it bundles the 1.x candidates | 2026-10-12 | accepted | | [0043](0043-exceptions-type-argument-constraints-constructor-guarantee.md) | Exceptions, type-argument constraints, a constructor guarantee and the text guards join 2.0.0 | 2026-10-13 | accepted | -| [0044](0044-2-0-0-net10-0-metalama.md) | 2.0.0 is `net10.0` on Metalama 2026.1; the version number names the API, never the runtime | 2026-10-13 | accepted; refined by [0120](0120-a-net11-0-target-keeps-every-build-and-csharp-15-waits-for-3-0-0.md) | +| [0044](0044-2-0-0-net10-0-metalama.md) | 2.0.0 is `net10.0` on Metalama 2026.1; the version number names the API, never the runtime | 2026-10-13 | accepted; refined by [0120](0120-a-net11-0-target-keeps-every-build-and-may-add-unions.md) | | [0045](0045-topic-pages-docs-compiled-like-readmes.md) | Topic pages in `docs/`, compiled like the READMEs; the plan in `design/` | 2026-10-13 | accepted | | [0046](0046-timeout-caught-result-catch-error.md) | A timeout caught by `Result.Catch` is the error | 2026-10-13 | accepted | | [0047](0047-firstornone-query-runs-query.md) | `FirstOrNone` on a query runs in the query | 2026-10-13 | accepted | @@ -126,4 +126,4 @@ per area ([design](../plan.md)) link to the records they rest on. | [0117](0117-a-seed-is-caught-against-a-passing-baseline.md) | `oracles seeds` credits a seed only when its oracle passes without it, in a baseline run with the same build and filter, and fails with it in a test of its own; a run that never reached the tests (a fixture xunit could not set up, an error counted outside any test, a crashed host, no TRX, every test skipped) or a failing baseline is `ERROR`, exit 4, never `CAUGHT` | 2026-10-29 | accepted | | [0118](0118-an-error-may-learn-a-form-only-new-api-can-write.md) | An Error may report, in a minor version, a form that only an API new in that version can write, since no build that worked before contains it: CMTK0003's dropped assertion of `CodoMetis.TypeKit.Testing` on a `Task` or `ValueTask` in 2.3.0, its `ConfigureAwait`, `WaitAsync` and branch forms (#152) included, known by the package's assembly; the forms freeze at the tag; refines decision 34; one exception the maintainer took into 2.3.0, a dropped `Task.WhenAny` of tasks of results (#159) | 2026-10-29 | accepted; widened by [0119](0119-a-gap-found-before-the-tag-ships-in-that-release.md) | | [0119](0119-a-gap-found-before-the-tag-ships-in-that-release.md) | A form an Error misses, found before its version is tagged, is reported in that version when it loses what the rule protects in every build, though code built on the previous minor can contain it: 2.3.0's CMTK0003 on a `Task.WhenAny` of results (#159), `Wait()` on a branch (#160), an array in `Task.WaitAll`/`WaitAny` (#161), a dropped `Task.WhenEach` (#162), and a `WhenAny` of held tasks silent; the rule stops at the tag, after which decision 34 holds; refines 34 and 118 | 2026-10-29 | accepted | -| [0120](0120-a-net11-0-target-keeps-every-build-and-csharp-15-waits-for-3-0-0.md) | A `net11.0` target joins a 2.x minor only if it keeps every consumer's build: the `net10.0` group's dependency floors (a higher floor on `net11.0` is NU1605 for an application that holds the lower version, measured on SDK 11 RC1), a superset surface that needs no C# 15 to call, the same generated members on every target, `LangVersion` 14 until an implementation needs 15, each target run; the SDK 11 spike comes first (#170); 3.0.0 stays C# 15 unions and the `net10.0` drop, timed to .NET 12 LTS, and .NET 11 is no reason for a major; refines decision 44 | 2026-10-29 | accepted | +| [0120](0120-a-net11-0-target-keeps-every-build-and-may-add-unions.md) | A `net11.0` target joins a 2.x minor only if it keeps every consumer's build: the `net10.0` group's dependency floors (a higher floor on `net11.0` is NU1605 for an application that holds the lower version, measured on SDK 11 RC1), a superset surface whose shared members behave the same, the same generated members on every target, `LangVersion` 15 for `net11.0` only once it uses a C# 15 feature, each target run; a union new on `net11.0` is an addition and may join a 2.x minor (a `LangVersion` 14 consumer who matches on it gets CS9327, measured); the SDK 11 spike comes first (#170); 3.0.0 stays the `net10.0` drop and a change of an existing contract, timed to .NET 12 LTS, and .NET 11 is no reason for a major; refines decision 44 | 2026-10-29 | accepted | diff --git a/design/roadmap.md b/design/roadmap.md index 737773e..9fa9623 100644 --- a/design/roadmap.md +++ b/design/roadmap.md @@ -63,11 +63,12 @@ Small, each closable in a minor version. Found by the reviews named. the packed generators still build and weave, measured as the fabric spike measured 2026.1 against the 2027.0 preview, and a major otherwise; `[Durable]` returns to the `_value` placeholder with it. - A `net11.0` target beside `net10.0`, in a 2.x minor when a feature needs it ([decision 44](adr/0044-2-0-0-net10-0-metalama.md)), - on the terms of [decision 120](adr/0120-a-net11-0-target-keeps-every-build-and-csharp-15-waits-for-3-0-0.md): the + on the terms of [decision 120](adr/0120-a-net11-0-target-keeps-every-build-and-may-add-unions.md): the `net10.0` group's dependency floors kept, since a higher floor on `net11.0` fails an application that holds the lower version (NU1605); the satellites tested against EF Core 11 and Microsoft.AspNetCore.OpenApi 11, not - compiled against them; the same generated members on every target; `LangVersion` 14 until an implementation - needs C# 15; the tests, a Native AOT smoke run and package validation per target. First the spike on an SDK 11 + compiled against them; the same generated members on every target; `LangVersion` 14 for `net10.0` and 15 for + `net11.0` once it uses a C# 15 feature, a union a new `net11.0`-only member among them; the tests, a Native AOT + smoke run and package validation per target. First the spike on an SDK 11 compiler, no release attached (#170): `global.json` pins SDK 10.0.4xx because the Roslyn band changes what Metalama emits, and the SDK is generally available in November 2026. @@ -89,11 +90,11 @@ Small, each closable in a minor version. Found by the reviews named. warning ([decision 90](adr/0090-faults-alike-are-a-warning-until-3-0.md)). - CMTK1023, a base record's equality that the generated one does not call, becomes an Error ([decision 75](adr/0075-equality-over-base-record-generated.md)). -- C# 15 unions and the `net10.0` drop, timed to .NET 12 LTS (November 2027) or to the end of .NET - 10's life, whichever its consumers need ([decision 44](adr/0044-2-0-0-net10-0-metalama.md), kept for .NET 11 - by [decision 120](adr/0120-a-net11-0-target-keeps-every-build-and-csharp-15-waits-for-3-0-0.md): .NET 11 is not a - reason for a major, and a floor an update would raise, a public C# 15 form or a member on one target only is). - The compiler-made union types have a public +- The `net10.0` drop, and a change of `Option` or `Result` into C# 15 unions, timed to .NET 12 LTS (November + 2027) or to the end of .NET 10's life, whichever its consumers need ([decision 44](adr/0044-2-0-0-net10-0-metalama.md), + kept for .NET 11 by [decision 120](adr/0120-a-net11-0-target-keeps-every-build-and-may-add-unions.md): .NET 11 + is not a reason for a major, and a change to a member both targets have is). A union that is new on `net11.0` + is an addition, and may join a 2.x minor. The compiler-made union types have a public `object? Value` and are serialized by System.Text.Json, so `Option` and `Result` take part in their - pattern matching only after a spike shows neither returns, on a branch of its own with no release - attached. + pattern matching, in either release, only after a spike shows neither returns, on a branch of its own with no + release attached. From 66115bff31710e2457eb888efa10f741c0942d93 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ricardo=20Gro=C3=9F?= Date: Thu, 29 Oct 2026 21:22:43 +0100 Subject: [PATCH 3/4] docs: decision 120 moves Metalama to 2027.0 in 2.5.0 for both targets Metalama is one version for every target and TypeKit follows the maintained Metalama year in a minor (1.5.0 already raised the floor in one). The move is not only a restore error: a woven net11.0 project goes from C# 14 to 15, an unsupported IDE host fails silently, and Metalama 2027 drops Visual Studio 2022. The record lists the conditions (stable 2027.0, a license-free CI and clean-container build, the ceiling and the 2.4.x line documented), corrects the rule on the language version and the SDK, and adds the measurements. Refs #169, #170, #173 Co-Authored-By: Claude Sonnet 5.5 --- ...et-keeps-every-build-and-may-add-unions.md | 103 ++++++++++++++++-- design/adr/README.md | 2 +- design/roadmap.md | 13 ++- 3 files changed, 106 insertions(+), 12 deletions(-) diff --git a/design/adr/0120-a-net11-0-target-keeps-every-build-and-may-add-unions.md b/design/adr/0120-a-net11-0-target-keeps-every-build-and-may-add-unions.md index cd1716e..973865b 100644 --- a/design/adr/0120-a-net11-0-target-keeps-every-build-and-may-add-unions.md +++ b/design/adr/0120-a-net11-0-target-keeps-every-build-and-may-add-unions.md @@ -7,7 +7,8 @@ Context: 2026-10-29, issue #169; refines [decision 44](0044-2-0-0-net10-0-metala `net11.0` target is additive and joins a 2.x minor when a feature needs it, and that unions and the `net10.0` drop are 3.0.0, timed to .NET 12 LTS. The maintainer asked what making 3.0.0 the .NET 11 release would change, and decided, in these words: "stick to the original plans regarding 3.0.0, add details to new 2.x -minors and .NET 11 related specifics". .NET 11 is a release candidate (SDK 11.0.100-rc.1 measured here, RC2 +minors and .NET 11 related specifics". On Metalama 2027 the maintainer decided the same day (see the +Metalama paragraph). .NET 11 is a release candidate (SDK 11.0.100-rc.1 measured here, RC2 named in [KB-046](../knowledge/KB-046-csharp-15-unions-ship-with-the-net-11-sdk.md)), with general availability expected in November 2026. @@ -39,7 +40,7 @@ It holds when: on update (measured below). The satellites compile against the floors they have and are *tested* against EF Core 11 and OpenAPI 11. A feature that needs an API only EF Core 11 or OpenAPI 11 has waits for 3.0.0, unless it lives in a package no consumer has installed yet. Raising a floor is a release decision, and in - a 2.x minor it is refused. + a 2.x minor it is refused, except for Metalama, which has a rule of its own below. 2. **Its public surface is a superset of `net10.0`'s, and a shared member behaves the same on both.** A member only `net11.0` has is available on that target alone, and the changelog says so. That includes members that use C# 15 unions or `closed` types: the `net10.0` asset has none of them, and a `net11.0` consumer who does @@ -49,15 +50,18 @@ It holds when: member are this repository's own and are decided by the unions spike the roadmap names, not by the version: it gives no path to the content of an `Option` or `Result` past `Match` and `TryGetValue` (a compiler-made union has a public `object? Value`), and it is not written by System.Text.Json where `Option` and `Result` - refuse it. + refuse it. A project that has the Generators package, on Metalama 2026.1, can neither declare nor match a + union (measured below), so until 2.5.0 a union member reaches the consumers without the Generators package + and those that reference Metalama 2027 themselves. 3. **Generated members are the same on every target.** The generators run in the consumer's compiler whatever the asset, and a value object's generated surface is the contract. A member that exists on one target only would make a consumer that multi-targets see two value objects under one name; any member the generators introduce on `net11.0` is introduced on `net10.0` too, or not at all. 4. **`LangVersion` stays 14 for `net10.0`, and is 15 for `net11.0` once its code uses a C# 15 feature**, in an implementation or in a new member's signature. The consumer needs nothing from the compiler the assembly was - built with, except to use a union or a `closed` type, which needs C# 15 and says so (CS9327). Generated - code compiles under C# 14 and under the C# 15 that SDK 11 defaults a `net11.0` consumer to. + built with, except to use a union or a `closed` type, which needs C# 15 and says so (CS9327). In a project + that has the Generators package, Metalama's compiler sets the language version, not the SDK: 14 on both + targets with Metalama 2026.1, 15 on `net11.0` with 2027 (measured below). Generated code compiles under both. 5. **Each target is run, not assumed:** the tests, the Native AOT smoke run and package validation per target, and the smoke test's consumer on `net11.0` that holds the lower satellite versions. @@ -66,7 +70,65 @@ Metalama and the analyzers emit. A `net11.0` target needs SDK 11, which changes assets too, and a consumer on SDK 11 meets it already with the 2.x `net10.0` asset. Before any `net11.0` target, a spike with no release attached (#170) measures on an SDK 11 compiler: the build and tests, the packed `net10.0` assets unchanged, a consumer on SDK 11 building and weaving, and the smoke test's SDK 11 lane. The -pin moves by a record of its own, after the SDK is generally available. +pin moves by a record of its own, after the SDK is generally available. The pin protects the packages that +Metalama does not touch and this repository's own build: in a project woven by Metalama the SDK does not choose +the compiler either, Metalama's does, so what such a project compiles with follows the Metalama year. + +**Metalama is one version for every target of a release, and TypeKit follows the maintained Metalama year in a +minor.** The Generators package stays on Metalama 2026.1 in 2.3 and 2.4, and 2.5.0 moves both targets to 2027.0 +once it is stable. The version never differs between the `net10.0` and `net11.0` assets: NuGet resolves one +`Metalama.Framework` per project, that version brings the engine and Metalama's compiler, and different floors +per target would give a consumer that multi-targets two compilers for its two inner builds (LAMA0054 refuses a +project whose `Metalama.Framework` is newer than its engine). This is a standing rule for Metalama alone, not +a general permission: every other dependency keeps the first rule. + +It is not new. 1.5.0, a minor, raised the floor from 2026.1.28 to 2026.1.29 (`CHANGELOG.md`, 1.5.0 "Changed"), +the same restore mechanism in a smaller step, because ADR 4 follows the only line Metalama's MIT maintenance +covers, the latest `YYYY.N`. Semantic Versioning's FAQ calls an update of a library's own dependencies with an +unchanged public API compatible, and leaves the conflicts to the dependents. The maintainer decided the move, +in these words: "every consumer that somehow needs Metalama 2026.x pinned for whatever he wants to achieve is +hitting TypeKit's version ceiling, so can continue using any TypeKit version that still allows any Metalama +2026 version; from 2.5.0 by depending my own packages on Metalama 2027 this is a clear, transparent ceiling". +Holding 2026.1 has a cost too: on SDK 11 it reports LAMA0617, its Razor analyzer needing a Roslyn newer than +the one it bundles (the review of 2026-10-29; a warning, falling back to the SDK 10 Razor compiler). + +**What a consumer sees is not only a restore error.** A project that references `Metalama.Framework` 2026.1 +directly gets NU1605, loud, with a one-line way out (raise it) or the line it already has (TypeKit 2.4.x). The +rest is quieter, and the documents say so: + +- a woven `net11.0` project moves from C# 14 to C# 15, with C# 15's own breaking changes (`union` and `closed` + become contextual keywords, `with(...)` in a collection expression binds differently); +- an IDE host Metalama 2027 does not support shows no Metalama diagnostics, code lens or generated code, and + Visual Studio 2022 gets a LAMA0602 warning at build (Metalama's platform support document); +- Metalama 2027 needs .NET SDK 10 or newer and declares 11 as its newest, so 3.0.0, or any SDK 12 build, will + need Metalama 2028; +- a consumer on `Metalama.Patterns` or `Metalama.Extensions` 2026.1 has a floor and no ceiling, so it resolves to + Framework 2027 with no restore error (inferred; the manifest format is 1 in both, so LAMA0061 should not + fire; the mix is untested). + +The move holds when: + +1. **Metalama 2027.0 is stable.** A stable package should not depend on a prerelease: NU5104 is a warning, and + an error under the warnings-as-errors this repository sets. +2. **A build with no Metalama license passes** in CI against it, as `dotnet.yml` has every job on 2026.1, and in + a clean container. The preview passed in a clean container (below); the general-availability build is + repeated, then a CI branch with the floor raised. A conventions test keeps `Metalama.Licensing`, whose + description says it fails a build without a valid license, out of the restore output. +3. **The weaving review and the smoke test run on 2027.0:** a consumer on Metalama 2027 beside the packages, + one with a direct 2026.1 reference that shows the NU1605 the changelog describes, one on `Metalama.Patterns` + 2026.1, and the design-time experience in Visual Studio, Rider and VS Code, which no command line shows. + The `[Durable]` placeholder in `ValueObjectAspect.Templates.cs` returns with the upgrade. +4. **The ceiling is written where a consumer reads it:** 2.5.0's changelog "Changed" and the Generators README + name the Metalama version and both ways out, the language-version change on `net11.0`, the minimum IDE + versions with the silent failure named, and what Metalama 2027 drops (Visual Studio 2022, .NET SDK 8 and 9). +5. **The line below the ceiling is declared:** whether 2.4.x gets patches after 2.5.0, and whether Metalama + 2026.1 is still maintained then. ADR 4 says MIT maintenance covers only the latest `YYYY.N`, while Metalama's + versioning policy page, as summarised, lists 2026.1 as a long-term release; the two are not reconciled. + +A consumer that needs Metalama 2027 before TypeKit moves does not wait: a direct reference beside 2.x weaves +(measured below, on the command line only). Metalama checks an order, not equality: compile-time code from +another version is accepted when its manifest format matches (LAMA0061; both read in the 2026.1.31 and +2027.0.4-preview sources by the research of 2026-10-29). **The 2.x line after 3.0.0** is decided with 3.0.0, not here. @@ -93,6 +155,28 @@ The SDK needs no `preview` flag for any of it (RC1), as [KB-046](../knowledge/KB says of RC2. So a union is addable on `net11.0` in a minor. What measurement one cannot make from this repository yet is the unions spike's: what `Option` and `Result` may offer a union without losing the guarantees above. +**Metalama.** `net11.0` console projects on SDK 11.0.100-rc.1 with the 2.2.0 packages (Metalama 2026.1), a +value object declared and used: + +- no union anywhere: builds and weaves; +- a `union` declared in the project: `CS0106`, `CS8803` and `CS1001`, since Metalama's compiler runs the project + at language version 14, where unions are still a preview feature (the same declaration compiles with no + Metalama); +- a union returned by a `net11.0`-only library: holding it builds; matching on it: `CS8652`, "unions are in + preview", where the same match compiles without Metalama. + +The same project with a direct `Metalama.Framework 2027.0.4-preview` reference beside the 2.2.0 packages +weaves the value object, and a declared union with a `switch` over it builds and runs. `#error version` in the same +projects names the compiler: Metalama's `2026.1.20` at language version 14.0 on `net10.0` and on `net11.0`, and +`2027.0.4-preview` at 15.0 on `net11.0`, whatever the SDK. One limit on my runs: this machine has a Metalama +license entry for 2027.0 in its licensing file and compiler servers were already running, so they say nothing +about a build without a license. CI on 2026.1 does (`dotnet.yml`, every job runs with no Metalama license on +the machine), and so did the review's run in a clean `mcr.microsoft.com/dotnet/sdk:10.0` container with +`UseSharedCompilation=false` and warnings as errors: a `net10.0` application with the 2.2.0 packages and +`Metalama.Framework 2027.0.4-preview` restored, wove and ran (`From`, `Parse`, JSON), and created no licensing +file; the same with 2026.1.30. Not established: the general-availability build, the IDEs, and +`Metalama.Patterns` beside it. + Adding a target is not additive by itself, though. The roadmap's "EF Core 11 and Microsoft.AspNetCore.OpenApi 11 in the satellites" is that case, and reads as the test lane in the first rule above from now on. @@ -103,4 +187,9 @@ Adding a target is not additive by itself, though. The roadmap's "EF Core 11 and union that changes an existing member; the maintainer's reading of Microsoft's compatibility guidance (a newer target may add to an older one's surface), checked against the measurement above. - Decision 44 stands; its index row says it is refined here. -- Whether Metalama 2026.1 and the analyzers hold under the SDK 11 compiler is open until #170. +- Metalama 2026.1 and the 2.2.0 packages build and weave a `net11.0` project on SDK 11 RC1 (measured above). + What #170 still owns is the rest of the SDK 11 spike, the general-availability SDKs and `rc.3` packages, the + analyzers on that compiler, the NU1605 measurement repeated there, and the license-free build on Metalama 2027. +- The roadmap's Metalama 2027.0 item becomes 2.5.0 (#173), on the conditions above. Its old test, "a minor if a + consumer's own aspects still build and weave", missed the restore error of a direct lower reference, the + language-version change and the IDE hosts. diff --git a/design/adr/README.md b/design/adr/README.md index d655115..e387a16 100644 --- a/design/adr/README.md +++ b/design/adr/README.md @@ -126,4 +126,4 @@ per area ([design](../plan.md)) link to the records they rest on. | [0117](0117-a-seed-is-caught-against-a-passing-baseline.md) | `oracles seeds` credits a seed only when its oracle passes without it, in a baseline run with the same build and filter, and fails with it in a test of its own; a run that never reached the tests (a fixture xunit could not set up, an error counted outside any test, a crashed host, no TRX, every test skipped) or a failing baseline is `ERROR`, exit 4, never `CAUGHT` | 2026-10-29 | accepted | | [0118](0118-an-error-may-learn-a-form-only-new-api-can-write.md) | An Error may report, in a minor version, a form that only an API new in that version can write, since no build that worked before contains it: CMTK0003's dropped assertion of `CodoMetis.TypeKit.Testing` on a `Task` or `ValueTask` in 2.3.0, its `ConfigureAwait`, `WaitAsync` and branch forms (#152) included, known by the package's assembly; the forms freeze at the tag; refines decision 34; one exception the maintainer took into 2.3.0, a dropped `Task.WhenAny` of tasks of results (#159) | 2026-10-29 | accepted; widened by [0119](0119-a-gap-found-before-the-tag-ships-in-that-release.md) | | [0119](0119-a-gap-found-before-the-tag-ships-in-that-release.md) | A form an Error misses, found before its version is tagged, is reported in that version when it loses what the rule protects in every build, though code built on the previous minor can contain it: 2.3.0's CMTK0003 on a `Task.WhenAny` of results (#159), `Wait()` on a branch (#160), an array in `Task.WaitAll`/`WaitAny` (#161), a dropped `Task.WhenEach` (#162), and a `WhenAny` of held tasks silent; the rule stops at the tag, after which decision 34 holds; refines 34 and 118 | 2026-10-29 | accepted | -| [0120](0120-a-net11-0-target-keeps-every-build-and-may-add-unions.md) | A `net11.0` target joins a 2.x minor only if it keeps every consumer's build: the `net10.0` group's dependency floors (a higher floor on `net11.0` is NU1605 for an application that holds the lower version, measured on SDK 11 RC1), a superset surface whose shared members behave the same, the same generated members on every target, `LangVersion` 15 for `net11.0` only once it uses a C# 15 feature, each target run; a union new on `net11.0` is an addition and may join a 2.x minor (a `LangVersion` 14 consumer who matches on it gets CS9327, measured); the SDK 11 spike comes first (#170); 3.0.0 stays the `net10.0` drop and a change of an existing contract, timed to .NET 12 LTS, and .NET 11 is no reason for a major; refines decision 44 | 2026-10-29 | accepted | +| [0120](0120-a-net11-0-target-keeps-every-build-and-may-add-unions.md) | A `net11.0` target joins a 2.x minor only if it keeps every consumer's build: the `net10.0` group's dependency floors (a higher floor on `net11.0` is NU1605 for an application that holds the lower version, measured on SDK 11 RC1), a superset surface whose shared members behave the same, the same generated members on every target, `LangVersion` 15 for `net11.0` only once it uses a C# 15 feature, each target run; a union new on `net11.0` is an addition and may join a 2.x minor (a `LangVersion` 14 consumer who matches on it gets CS9327, measured); Metalama is one version for every target and TypeKit follows the maintained Metalama year in a minor: 2.3 and 2.4 stay on 2026.1, 2.5.0 moves both targets to 2027.0 once stable (a direct 2026.1 reference gets NU1605 and stays on 2.4.x; the move also changes a woven `net11.0` project from C# 14 to 15 and needs a supported IDE; conditions: a license-free CI and clean-container build, the ceiling and the 2.4.x line documented); the SDK 11 spike comes first (#170); 3.0.0 stays the `net10.0` drop and a change of an existing contract, timed to .NET 12 LTS, and .NET 11 is no reason for a major; refines decision 44 | 2026-10-29 | accepted | diff --git a/design/roadmap.md b/design/roadmap.md index 9fa9623..adf3fc7 100644 --- a/design/roadmap.md +++ b/design/roadmap.md @@ -58,10 +58,15 @@ Small, each closable in a minor version. Found by the reviews named. converters' trimming warnings decide whether it is a package ([decision 71](adr/0071-converters-say-what-they-write.md)). - A stable external name for a closed-set member, apart from its C# identifier: parsing, JSON, EF, OpenAPI and the fault identifiers at once (proposed in the review of 2026-10-13). -- The Metalama 2027.0 upgrade, once it is stable (`2027.0.4-preview` on 2026-10-14; - [decision 4](adr/0004-metalama-2026-1-x-latest-stable.md)): a minor if a consumer's own aspects beside - the packed generators still build and weave, measured as the fabric spike measured 2026.1 against the - 2027.0 preview, and a major otherwise; `[Durable]` returns to the `_value` placeholder with it. +- The Metalama 2027.0 upgrade, in 2.5.0 (#173), once 2027.0 is stable (`2027.0.4-preview` on 2026-10-14; + [decision 4](adr/0004-metalama-2026-1-x-latest-stable.md)), for both targets at once and with 2.3 and 2.4 on + 2026.1 ([decision 120](adr/0120-a-net11-0-target-keeps-every-build-and-may-add-unions.md)). It raises a floor in + a minor, which is Metalama's standing rule alone: a project that references `Metalama.Framework` 2026.1 + directly gets NU1605 and stays on TypeKit 2.4.x or raises its reference, and a woven `net11.0` project moves + from C# 14 to 15. Its conditions: a build with no Metalama license passes in CI and a clean container on 2027 + (#170), the weaving review and the smoke test run on it, the changelog and the Generators README name the + ceiling, the language-version change, the supported IDEs and what Metalama 2027 drops, and the release notes + say whether 2.4.x is patched. `[Durable]` returns to the `_value` placeholder with it. - A `net11.0` target beside `net10.0`, in a 2.x minor when a feature needs it ([decision 44](adr/0044-2-0-0-net10-0-metalama.md)), on the terms of [decision 120](adr/0120-a-net11-0-target-keeps-every-build-and-may-add-unions.md): the `net10.0` group's dependency floors kept, since a higher floor on `net11.0` fails an application that holds the From 0ec904f5029ce6b2f8b3a7873232c975d31b1dde Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ricardo=20Gro=C3=9F?= Date: Thu, 29 Oct 2026 21:30:14 +0100 Subject: [PATCH 4/4] docs: decision 120 allows a documented dependency-floor raise in a minor The Semantic Versioning FAQ calls a dependency update with an unchanged public API compatible, so the first rule no longer refuses a higher floor in a minor, on every target or on net11.0 alone. The raise is documented instead: the changelog names the dependency, the floors, who it reaches (NU1605 for a project that holds the lower version) and both ways out, and the smoke test shows it. Metalama is the same rule on the dependency with the widest reach. The record is renamed to say so; the roadmap and the index row follow. Refs #169 Co-Authored-By: Claude Sonnet 5.5 --- ...d-documented-floor-raises-join-a-minor.md} | 48 +++++++++++-------- design/adr/README.md | 4 +- design/roadmap.md | 14 +++--- 3 files changed, 37 insertions(+), 29 deletions(-) rename design/adr/{0120-a-net11-0-target-keeps-every-build-and-may-add-unions.md => 0120-a-net11-0-target-and-documented-floor-raises-join-a-minor.md} (84%) diff --git a/design/adr/0120-a-net11-0-target-keeps-every-build-and-may-add-unions.md b/design/adr/0120-a-net11-0-target-and-documented-floor-raises-join-a-minor.md similarity index 84% rename from design/adr/0120-a-net11-0-target-keeps-every-build-and-may-add-unions.md rename to design/adr/0120-a-net11-0-target-and-documented-floor-raises-join-a-minor.md index 973865b..e96d7ea 100644 --- a/design/adr/0120-a-net11-0-target-keeps-every-build-and-may-add-unions.md +++ b/design/adr/0120-a-net11-0-target-and-documented-floor-raises-join-a-minor.md @@ -1,4 +1,4 @@ -# 0120. A `net11.0` target joins a 2.x minor only if it keeps every build, and may add C# 15 unions; 3.0.0 stays the change of an existing contract +# 0120. A `net11.0` target and a documented dependency-floor raise join a 2.x minor, and so may a C# 15 union; 3.0.0 stays the change of an existing contract Date: 2026-10-29 Status: accepted @@ -7,8 +7,8 @@ Context: 2026-10-29, issue #169; refines [decision 44](0044-2-0-0-net10-0-metala `net11.0` target is additive and joins a 2.x minor when a feature needs it, and that unions and the `net10.0` drop are 3.0.0, timed to .NET 12 LTS. The maintainer asked what making 3.0.0 the .NET 11 release would change, and decided, in these words: "stick to the original plans regarding 3.0.0, add details to new 2.x -minors and .NET 11 related specifics". On Metalama 2027 the maintainer decided the same day (see the -Metalama paragraph). .NET 11 is a release candidate (SDK 11.0.100-rc.1 measured here, RC2 +minors and .NET 11 related specifics". On Metalama 2027, and on floor raises in general, the maintainer decided +the same day (see the first rule and the Metalama paragraph). .NET 11 is a release candidate (SDK 11.0.100-rc.1 measured here, RC2 named in [KB-046](../knowledge/KB-046-csharp-15-unions-ship-with-the-net-11-sdk.md)), with general availability expected in November 2026. @@ -29,18 +29,24 @@ and their pattern matching would differ between two versions of the same member. exists on `net11.0` only, is an addition like any other, and may join a 2.x minor (second rule below). A minor that fixes bugs for every consumer and adds to `net11.0` alone is a plain 2.x minor. -**A `net11.0` asset joins a 2.x minor when a feature needs it, and only if it keeps every consumer's build.** -No patch adds a target. The next minor after the one in flight carries it; the number is not reserved here. -It holds when: - -1. **Its dependency groups keep the `net10.0` group's floors.** An application on `net11.0` may hold the lower - version of a satellite's dependency (EF Core 10 and Microsoft.AspNetCore.OpenApi 10 are `net10.0` packages, - which a `net11.0` project may reference). - NuGet picks the `net11.0` group for it, and a floor that group raises is a downgrade error, NU1605, - on update (measured below). The satellites compile against the floors they have and are *tested* against - EF Core 11 and OpenAPI 11. A feature that needs an API only EF Core 11 or OpenAPI 11 has waits for 3.0.0, - unless it lives in a package no consumer has installed yet. Raising a floor is a release decision, and in - a 2.x minor it is refused, except for Metalama, which has a rule of its own below. +**A `net11.0` asset joins a 2.x minor when a feature needs it, on these rules.** No patch adds a target. The next +minor after the one in flight carries it; the number is not reserved here. + +1. **A dependency floor may rise in a minor, documented, on every target or on `net11.0` alone.** It is a + dependency update with an unchanged public API, which the Semantic Versioning FAQ calls compatible, leaving the + conflict to whoever references the dependency at a lower version. The conflict is real, and measured: an + application on `net11.0` that holds EF Core 10 (or Microsoft.AspNetCore.OpenApi 10) while the `net11.0` group + requires 11 fails restore with NU1605, an error by default (below). So a raise: + - has a reason, a feature, a fix or a supported version, and is the lowest floor that does the work; + - is a release decision named in the changelog "Changed": the dependency, the old and the new floor, the + targets it covers, who it reaches (a project that references the dependency at a lower version) and both + ways out (raise that reference, or stay on the previous minor); + - changes no public API and no generated member; a consequence beyond the restore (a compiler, a language + version, a supported IDE, a translation, a schema) is assessed on its own and documented, as Metalama's is + below; + - is shown by the smoke test: a consumer that holds the lower version gets the NU1605 the changelog + describes. The satellites may compile against EF Core 11 and OpenAPI 11 and require them in the `net11.0` + group. 2. **Its public surface is a superset of `net10.0`'s, and a shared member behaves the same on both.** A member only `net11.0` has is available on that target alone, and the changelog says so. That includes members that use C# 15 unions or `closed` types: the `net10.0` asset has none of them, and a `net11.0` consumer who does @@ -76,11 +82,12 @@ the compiler either, Metalama's does, so what such a project compiles with follo **Metalama is one version for every target of a release, and TypeKit follows the maintained Metalama year in a minor.** The Generators package stays on Metalama 2026.1 in 2.3 and 2.4, and 2.5.0 moves both targets to 2027.0 -once it is stable. The version never differs between the `net10.0` and `net11.0` assets: NuGet resolves one +once it is stable. Other floors may differ between targets (first rule); this one never does: NuGet resolves one `Metalama.Framework` per project, that version brings the engine and Metalama's compiler, and different floors per target would give a consumer that multi-targets two compilers for its two inner builds (LAMA0054 refuses a -project whose `Metalama.Framework` is newer than its engine). This is a standing rule for Metalama alone, not -a general permission: every other dependency keeps the first rule. +project whose `Metalama.Framework` is newer than its engine). The raise itself is the first rule applied to the +dependency with the widest reach: Metalama is also the compiler of the consumer's woven projects, so it carries +the extra consequences below. It is not new. 1.5.0, a minor, raised the floor from 2026.1.28 to 2026.1.29 (`CHANGELOG.md`, 1.5.0 "Changed"), the same restore mechanism in a smaller step, because ADR 4 follows the only line Metalama's MIT maintenance @@ -177,8 +184,9 @@ the machine), and so did the review's run in a clean `mcr.microsoft.com/dotnet/s file; the same with 2026.1.30. Not established: the general-availability build, the IDEs, and `Metalama.Patterns` beside it. -Adding a target is not additive by itself, though. The roadmap's "EF Core 11 and Microsoft.AspNetCore.OpenApi -11 in the satellites" is that case, and reads as the test lane in the first rule above from now on. +Adding a target is not additive for a consumer on a lower version of a dependency, though. The roadmap's "EF Core +11 and Microsoft.AspNetCore.OpenApi 11 in the satellites" is that case. It is allowed and documented under the +first rule, and the test lane of that rule shows it. ## Consequences diff --git a/design/adr/README.md b/design/adr/README.md index e387a16..245695f 100644 --- a/design/adr/README.md +++ b/design/adr/README.md @@ -50,7 +50,7 @@ per area ([design](../plan.md)) link to the records they rest on. | [0041](0041-sensitive-value-object-declares-tostring-serilog.md) | A sensitive value object is one that declares `ToString()`; Serilog gets a satellite | 2026-10-11 | accepted | | [0042](0042-2-0-0-next-release-bundles.md) | 2.0.0 is the next release, and it bundles the 1.x candidates | 2026-10-12 | accepted | | [0043](0043-exceptions-type-argument-constraints-constructor-guarantee.md) | Exceptions, type-argument constraints, a constructor guarantee and the text guards join 2.0.0 | 2026-10-13 | accepted | -| [0044](0044-2-0-0-net10-0-metalama.md) | 2.0.0 is `net10.0` on Metalama 2026.1; the version number names the API, never the runtime | 2026-10-13 | accepted; refined by [0120](0120-a-net11-0-target-keeps-every-build-and-may-add-unions.md) | +| [0044](0044-2-0-0-net10-0-metalama.md) | 2.0.0 is `net10.0` on Metalama 2026.1; the version number names the API, never the runtime | 2026-10-13 | accepted; refined by [0120](0120-a-net11-0-target-and-documented-floor-raises-join-a-minor.md) | | [0045](0045-topic-pages-docs-compiled-like-readmes.md) | Topic pages in `docs/`, compiled like the READMEs; the plan in `design/` | 2026-10-13 | accepted | | [0046](0046-timeout-caught-result-catch-error.md) | A timeout caught by `Result.Catch` is the error | 2026-10-13 | accepted | | [0047](0047-firstornone-query-runs-query.md) | `FirstOrNone` on a query runs in the query | 2026-10-13 | accepted | @@ -126,4 +126,4 @@ per area ([design](../plan.md)) link to the records they rest on. | [0117](0117-a-seed-is-caught-against-a-passing-baseline.md) | `oracles seeds` credits a seed only when its oracle passes without it, in a baseline run with the same build and filter, and fails with it in a test of its own; a run that never reached the tests (a fixture xunit could not set up, an error counted outside any test, a crashed host, no TRX, every test skipped) or a failing baseline is `ERROR`, exit 4, never `CAUGHT` | 2026-10-29 | accepted | | [0118](0118-an-error-may-learn-a-form-only-new-api-can-write.md) | An Error may report, in a minor version, a form that only an API new in that version can write, since no build that worked before contains it: CMTK0003's dropped assertion of `CodoMetis.TypeKit.Testing` on a `Task` or `ValueTask` in 2.3.0, its `ConfigureAwait`, `WaitAsync` and branch forms (#152) included, known by the package's assembly; the forms freeze at the tag; refines decision 34; one exception the maintainer took into 2.3.0, a dropped `Task.WhenAny` of tasks of results (#159) | 2026-10-29 | accepted; widened by [0119](0119-a-gap-found-before-the-tag-ships-in-that-release.md) | | [0119](0119-a-gap-found-before-the-tag-ships-in-that-release.md) | A form an Error misses, found before its version is tagged, is reported in that version when it loses what the rule protects in every build, though code built on the previous minor can contain it: 2.3.0's CMTK0003 on a `Task.WhenAny` of results (#159), `Wait()` on a branch (#160), an array in `Task.WaitAll`/`WaitAny` (#161), a dropped `Task.WhenEach` (#162), and a `WhenAny` of held tasks silent; the rule stops at the tag, after which decision 34 holds; refines 34 and 118 | 2026-10-29 | accepted | -| [0120](0120-a-net11-0-target-keeps-every-build-and-may-add-unions.md) | A `net11.0` target joins a 2.x minor only if it keeps every consumer's build: the `net10.0` group's dependency floors (a higher floor on `net11.0` is NU1605 for an application that holds the lower version, measured on SDK 11 RC1), a superset surface whose shared members behave the same, the same generated members on every target, `LangVersion` 15 for `net11.0` only once it uses a C# 15 feature, each target run; a union new on `net11.0` is an addition and may join a 2.x minor (a `LangVersion` 14 consumer who matches on it gets CS9327, measured); Metalama is one version for every target and TypeKit follows the maintained Metalama year in a minor: 2.3 and 2.4 stay on 2026.1, 2.5.0 moves both targets to 2027.0 once stable (a direct 2026.1 reference gets NU1605 and stays on 2.4.x; the move also changes a woven `net11.0` project from C# 14 to 15 and needs a supported IDE; conditions: a license-free CI and clean-container build, the ceiling and the 2.4.x line documented); the SDK 11 spike comes first (#170); 3.0.0 stays the `net10.0` drop and a change of an existing contract, timed to .NET 12 LTS, and .NET 11 is no reason for a major; refines decision 44 | 2026-10-29 | accepted | +| [0120](0120-a-net11-0-target-and-documented-floor-raises-join-a-minor.md) | A `net11.0` target joins a 2.x minor when a feature needs it, and a dependency floor may rise in a minor, on every target or on `net11.0` alone, if it is documented (the Semantic Versioning FAQ calls a dependency update with an unchanged public API compatible; a project that references the dependency lower gets NU1605, measured on SDK 11 RC1, so the changelog names the floor, who it reaches and both ways out, and the smoke test shows it); a union new on `net11.0` is an addition and may join a 2.x minor (a `LangVersion` 14 consumer who matches on it gets CS9327, and on Metalama 2026.1 CS8652, measured); Metalama is one version for every target and TypeKit follows the maintained Metalama year in a minor: 2.3 and 2.4 stay on 2026.1 and 2.5.0 moves both targets to 2027.0 once stable (a direct 2026.1 reference gets NU1605 and stays on 2.4.x; a woven `net11.0` project goes from C# 14 to 15; conditions: a license-free CI and clean-container build, the ceiling and the 2.4.x line documented); the SDK 11 spike comes first (#170); 3.0.0 stays the `net10.0` drop and a change of an existing contract, timed to .NET 12 LTS, and .NET 11 is no reason for a major; refines decision 44 | 2026-10-29 | accepted | diff --git a/design/roadmap.md b/design/roadmap.md index adf3fc7..11d478e 100644 --- a/design/roadmap.md +++ b/design/roadmap.md @@ -60,18 +60,18 @@ Small, each closable in a minor version. Found by the reviews named. OpenAPI and the fault identifiers at once (proposed in the review of 2026-10-13). - The Metalama 2027.0 upgrade, in 2.5.0 (#173), once 2027.0 is stable (`2027.0.4-preview` on 2026-10-14; [decision 4](adr/0004-metalama-2026-1-x-latest-stable.md)), for both targets at once and with 2.3 and 2.4 on - 2026.1 ([decision 120](adr/0120-a-net11-0-target-keeps-every-build-and-may-add-unions.md)). It raises a floor in - a minor, which is Metalama's standing rule alone: a project that references `Metalama.Framework` 2026.1 + 2026.1 ([decision 120](adr/0120-a-net11-0-target-and-documented-floor-raises-join-a-minor.md)). It raises a floor in + a minor, which decision 120 allows when documented: a project that references `Metalama.Framework` 2026.1 directly gets NU1605 and stays on TypeKit 2.4.x or raises its reference, and a woven `net11.0` project moves from C# 14 to 15. Its conditions: a build with no Metalama license passes in CI and a clean container on 2027 (#170), the weaving review and the smoke test run on it, the changelog and the Generators README name the ceiling, the language-version change, the supported IDEs and what Metalama 2027 drops, and the release notes say whether 2.4.x is patched. `[Durable]` returns to the `_value` placeholder with it. - A `net11.0` target beside `net10.0`, in a 2.x minor when a feature needs it ([decision 44](adr/0044-2-0-0-net10-0-metalama.md)), - on the terms of [decision 120](adr/0120-a-net11-0-target-keeps-every-build-and-may-add-unions.md): the - `net10.0` group's dependency floors kept, since a higher floor on `net11.0` fails an application that holds the - lower version (NU1605); the satellites tested against EF Core 11 and Microsoft.AspNetCore.OpenApi 11, not - compiled against them; the same generated members on every target; `LangVersion` 14 for `net10.0` and 15 for + on the terms of [decision 120](adr/0120-a-net11-0-target-and-documented-floor-raises-join-a-minor.md): a higher + floor in the `net11.0` group (EF Core 11 and Microsoft.AspNetCore.OpenApi 11 in the satellites) allowed when the + changelog names it, who it reaches (an application that holds the lower version gets NU1605) and both ways out, + and the smoke test shows it; the same generated members on every target; `LangVersion` 14 for `net10.0` and 15 for `net11.0` once it uses a C# 15 feature, a union a new `net11.0`-only member among them; the tests, a Native AOT smoke run and package validation per target. First the spike on an SDK 11 compiler, no release attached (#170): `global.json` pins SDK 10.0.4xx because the Roslyn band changes what @@ -97,7 +97,7 @@ Small, each closable in a minor version. Found by the reviews named. ([decision 75](adr/0075-equality-over-base-record-generated.md)). - The `net10.0` drop, and a change of `Option` or `Result` into C# 15 unions, timed to .NET 12 LTS (November 2027) or to the end of .NET 10's life, whichever its consumers need ([decision 44](adr/0044-2-0-0-net10-0-metalama.md), - kept for .NET 11 by [decision 120](adr/0120-a-net11-0-target-keeps-every-build-and-may-add-unions.md): .NET 11 + kept for .NET 11 by [decision 120](adr/0120-a-net11-0-target-and-documented-floor-raises-join-a-minor.md): .NET 11 is not a reason for a major, and a change to a member both targets have is). A union that is new on `net11.0` is an addition, and may join a 2.x minor. The compiler-made union types have a public `object? Value` and are serialized by System.Text.Json, so `Option` and `Result` take part in their