From e70868a60c322ff95f719adc68c81f59873def35 Mon Sep 17 00:00:00 2001 From: Chrison Simtian Date: Sat, 30 May 2026 02:49:33 +1200 Subject: [PATCH] docs(experimental): establish [Experimental] API convention + FALLOUT0xx registry MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Implements ADR-0004 §5. Documents the [Experimental("FALLOUT0xx")] opt-in convention for unstable public APIs: the System.Diagnostics.CodeAnalysis attribute (BCL, no package), sequential never-reused diagnostic IDs, the consumer suppression opt-in, promote-to-stable = remove-the-attribute, and the edge-vs-stable-train channel discipline. Adds a diagnostic-ID registry at docs/experimental-apis.md (seeded empty) and an AGENTS.md pointer near rule #2. Docs-only: no [Experimental] applied to any existing API. Co-Authored-By: Claude Opus 4.8 (1M context) --- AGENTS.md | 2 +- docs/agents/conventions.md | 23 +++++++++++++++ docs/experimental-apis.md | 57 ++++++++++++++++++++++++++++++++++++++ 3 files changed, 81 insertions(+), 1 deletion(-) create mode 100644 docs/experimental-apis.md diff --git a/AGENTS.md b/AGENTS.md index 1fac69b96..f80a06cc3 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -47,7 +47,7 @@ Do not commit code generated by `GenerateTools` — generated code is regenerate ## Critical rules (read this every session) 1. **At PR-creation time, follow the [PR-creation flow](docs/agents/release-and-versioning.md#pr-creation-flow) in `docs/agents/release-and-versioning.md`.** Every PR gets a `target/YYYY` label (`target/2026`; legacy v10 work uses `target/v10`). Breaking changes additionally get a `breaking-change` label, a `⚠️ Breaking change` callout, and a `CHANGELOG.md` entry — recorded under the **next yearly major** (breaking changes are batched to the year cut, not shipped mid-year). A breaking-change PR may target **`main` only** — never a `release/YYYY` stable train. This is non-negotiable — review will block. -2. **Default to backwards compatibility.** Prefer additive over breaking changes. Before changing a public signature, removing an API, renaming a package, or altering an on-disk format, ask: can this be additive instead? `[Obsolete]` markers, transition shims (see `src/Shims/` + `Fallout.SourceGenerators.TransitionShimGenerator`), the `[Experimental("FALLOUT0xx")]` opt-in escape hatch for not-yet-stable surface, feature flags, and overload-based extension are all preferred to a hard break. When a breaking change is genuinely unavoidable, it lands on `main`, is held for the next yearly major, and follows rule #1's flow — the break must be deliberate, named, and migration-pathed in the CHANGELOG. See [#262](https://github.com/ChrisonSimtian/Fallout/issues/262) for the broader discussion. +2. **Default to backwards compatibility.** Prefer additive over breaking changes. Before changing a public signature, removing an API, renaming a package, or altering an on-disk format, ask: can this be additive instead? `[Obsolete]` markers, transition shims (see `src/Shims/` + `Fallout.SourceGenerators.TransitionShimGenerator`), the `[Experimental("FALLOUT0xx")]` opt-in escape hatch for not-yet-stable surface, feature flags, and overload-based extension are all preferred to a hard break. When a breaking change is genuinely unavoidable, it lands on `main`, is held for the next yearly major, and follows rule #1's flow — the break must be deliberate, named, and migration-pathed in the CHANGELOG. See [#262](https://github.com/ChrisonSimtian/Fallout/issues/262) for the broader discussion. The `[Experimental]` convention (diagnostic-ID scheme + registry) is documented in [docs/agents/conventions.md](docs/agents/conventions.md#experimental-for-opt-in-unstable-apis) and [docs/experimental-apis.md](docs/experimental-apis.md). 3. **Central package versions only** — add to `Directory.Packages.props`, never `Version=` inline. 4. **Tests next to code** — every `src/Foo` has a `tests/Foo.Tests` sibling. Mirror namespaces. 5. **Stay on xUnit + FluentAssertions + Verify.** Don't introduce new test frameworks. diff --git a/docs/agents/conventions.md b/docs/agents/conventions.md index 59b972c4c..d817f4a9f 100644 --- a/docs/agents/conventions.md +++ b/docs/agents/conventions.md @@ -10,6 +10,29 @@ Three groups: conventions to respect, things never to do, and the tool-wrapper r - **No IDE-specific style files committed.** `.editorconfig` and `*.DotSettings` were removed during the takeover — relying on `dotnet format` defaults and review. - **Telemetry opt-out is set in test runs** (`FALLOUT_TELEMETRY_OPTOUT=true`). Keep it that way. - **No per-file license headers.** The MIT notice lives in [`LICENSE`](../../LICENSE) at the repo root, and NuGet packages declare MIT via `PackageLicenseExpression`. Per-file headers were stripped in v11 (one source of truth + the header URL would have rotted on the repo-org transfer). Vendored third-party code keeps its own copyright headers — don't touch those (e.g. files under `src/Persistence/Fallout.Persistence.Solution/` retain Microsoft's MIT notice). +- **`[Experimental]` for opt-in unstable public APIs.** Not-yet-stable public surface is marked with `[Experimental("FALLOUT0xx")]` rather than held back or shipped silently. See [the `[Experimental]` convention](#experimental-for-opt-in-unstable-apis) below and the [diagnostic-ID registry](../experimental-apis.md). + +## `[Experimental]` for opt-in unstable APIs + +Per [ADR-0004 §5](../adr/0004-calendar-versioning-and-dual-pace-channels.md), public APIs that aren't ready to commit to a stability guarantee are marked with [`System.Diagnostics.CodeAnalysis.ExperimentalAttribute`](https://learn.microsoft.com/dotnet/api/system.diagnostics.codeanalysis.experimentalattribute) instead of being held back or shipped silently. The attribute ships in the .NET 8+ BCL — **no package reference needed** (the repo targets .NET 10). + +```csharp +using System.Diagnostics.CodeAnalysis; + +[Experimental("FALLOUT001")] +public sealed class NewPluginHost +{ + // ... +} +``` + +**Rules:** + +- **Diagnostic-ID scheme: `FALLOUT0xx`.** Each experimental surface gets its own ID (e.g. `FALLOUT001`), allocated **sequentially and never reused** — a retired ID stays retired. Register every allocation in the [diagnostic-ID registry](../experimental-apis.md) in the same PR that introduces it. +- **Consumers must explicitly opt in.** `ExperimentalAttribute` is an *error-by-default* diagnostic: code that touches the API fails to compile until the consumer suppresses the exact ID — `#pragma warning disable FALLOUT001` around the call site, or `$(NoWarn);FALLOUT001` in their project. Opting into instability is therefore a conscious, per-API choice — which is right for a *framework* (a product devs build on), not an app. +- **Promoting to stable = removing the attribute.** Because the feature already rode the trunk (`main`/edge), there is no cross-branch cherry-pick — deleting the `[Experimental]` line is the whole promotion. This is what lets stabilised work feed back into the regular channels without a divergent fork. Adding *or* removing `[Experimental]` is **not** a breaking change. +- **Channel discipline differs.** On `main`/edge, experimental churn is expected and the attribute is a courtesy. On a `release/YYYY` **stable train**, any risky-but-shipped public surface **must** wear `[Experimental]` — that contract is what keeps the stable line trustworthy while still carrying new work. +- **Don't apply it speculatively.** Because the diagnostic is error-by-default, marking an API that's already used internally breaks the build everywhere it's referenced. Only add `[Experimental]` to a genuinely not-yet-stable API, and suppress every internal usage in the same change so the build stays green. ## What not to do diff --git a/docs/experimental-apis.md b/docs/experimental-apis.md new file mode 100644 index 000000000..b56aa5fa9 --- /dev/null +++ b/docs/experimental-apis.md @@ -0,0 +1,57 @@ +# Experimental APIs + +Fallout marks not-yet-stable public APIs with [`System.Diagnostics.CodeAnalysis.ExperimentalAttribute`](https://learn.microsoft.com/dotnet/api/system.diagnostics.codeanalysis.experimentalattribute) so consumers opt into instability deliberately. This page is the **canonical registry of allocated `FALLOUT0xx` diagnostic IDs**. + +See [ADR-0004 §5](adr/0004-calendar-versioning-and-dual-pace-channels.md#5-experimental-for-opt-in-unstable-apis) for the decision and [the agent conventions](agents/conventions.md#experimental-for-opt-in-unstable-apis) for the contributor rules. + +## How it works + +An experimental API carries a diagnostic ID: + +```csharp +using System.Diagnostics.CodeAnalysis; + +[Experimental("FALLOUT001")] +public sealed class NewPluginHost +{ + // ... +} +``` + +`ExperimentalAttribute` is an **error-by-default** diagnostic. Code that touches the API will not compile until you explicitly suppress the exact ID, which is your conscious opt-in to an API that may change without notice: + +```csharp +#pragma warning disable FALLOUT001 // opting into an experimental API +var host = new NewPluginHost(); +#pragma warning restore FALLOUT001 +``` + +or, project-wide, in your `.csproj`: + +```xml + + $(NoWarn);FALLOUT001 + +``` + +## Diagnostic-ID scheme + +- IDs use the form `FALLOUT0xx` and are allocated **sequentially**. +- An ID is **never reused** — once retired it stays retired, so a suppression can never silently re-bind to a different API. +- Every allocation is recorded in the registry table below, in the same PR that introduces the attribute. +- **Promoting an API to stable means deleting the `[Experimental]` attribute** (the feature already rode the trunk — no cross-branch cherry-pick). The ID's row moves to **Promoted** status and the ID is retired, not recycled. Adding or removing `[Experimental]` is not a breaking change. +- **Channel discipline:** on `main`/edge the attribute is a courtesy; on a `release/YYYY` stable train any risky-but-shipped public surface **must** wear it. + +## Registry + +Status values: **Experimental** (live, opt-in), **Promoted** (attribute removed, now stable — ID retired), **Withdrawn** (API removed before promotion — ID retired). + +| ID | Surface | Introduced | Status | Notes | +|----|---------|------------|--------|-------| +| _none allocated yet_ | — | — | — | First experimental API to land claims `FALLOUT001`. | + +