diff --git a/AGENTS.md b/AGENTS.md index 50ec1584..4b29aec2 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -49,7 +49,7 @@ To restructure an existing PR's commit history into focused commits, use the `/r ## 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 targets **`main`** with the breaking surface gated behind `[Experimental("FALLOUT0xx")]` (or, when it can't be gated, on a short-lived topic branch off `main` held for the year cut) — **never** a `release/YYYY` production train. This is non-negotiable — review will block. (Before [ADR-0008](docs/adr/0008-collapse-experimental-into-main.md) breaking work targeted the `experimental` branch; that branch is gone.) -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` (gated behind `[Experimental("FALLOUT0xx")]`, or held on a short-lived topic branch 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/Fallout-build/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). +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` (gated behind `[Experimental("FALLOUT0xx")]`, or held on a short-lived topic branch 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/Fallout-build/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). Deprecations use `[Obsolete]` with a `FALLOUTOBS0xx` `DiagnosticId` so `TreatWarningsAsErrors` consumers can suppress a single deprecation — see [docs/agents/conventions.md](docs/agents/conventions.md#obsolete-for-deprecating-public-apis) and the [docs/obsolete_apis.md](docs/obsolete_apis.md) registry. 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 fee972ae..d14b74b7 100644 --- a/docs/agents/conventions.md +++ b/docs/agents/conventions.md @@ -13,6 +13,7 @@ Three groups: conventions to respect, things never to do, and the tool-wrapper r - **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`](https://github.com/Fallout-build/Fallout/blob/main/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). +- **`[Obsolete]` with a `DiagnosticId` for deprecations.** Deprecated public surface carries `[Obsolete(..., DiagnosticId = "FALLOUTOBS0xx")]` so `TreatWarningsAsErrors` consumers can suppress a single deprecation. See [the `[Obsolete]` convention](#obsolete-for-deprecating-public-apis) below and the [diagnostic-ID registry](../obsolete_apis.md). ## Writing style for issues, PRs, and commits @@ -48,6 +49,27 @@ public sealed class NewPluginHost - **Channel discipline differs.** On the `main` (preview) test lane, churn is expected and the attribute is a courtesy. On a `release/YYYY` **production line**, any risky-but-shipped public surface **must** wear `[Experimental]` — that contract is what keeps the stable line trustworthy while still carrying new work. With the `experimental` branch retired ([ADR-0008](../adr/0008-collapse-experimental-into-main.md)), `[Experimental]` is now the primary mechanism for isolating unstable surface on `main` — including breaking changes batched toward the yearly major. - **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. +## `[Obsolete]` for deprecating public APIs + +When a public API is on its way out, mark it with [`System.ObsoleteAttribute`](https://learn.microsoft.com/dotnet/api/system.obsoleteattribute) and give it a `DiagnosticId`. This is the sanctioned deprecation path under [AGENTS.md rule #2](../../AGENTS.md) — keep the old surface working (usually bridging to the replacement) while steering consumers to the new one. `DiagnosticId`/`UrlFormat` ship in the .NET 5+ BCL — **no package reference needed** (the repo targets .NET 10). + +```csharp +using System; + +[Obsolete( + "Use [GitHubActionsInputAttribute] instead. Removed in 2027.x.x.", + DiagnosticId = "FALLOUTOBS001", + UrlFormat = "https://github.com/Fallout-build/Fallout/blob/main/docs/obsolete_apis.md")] +public string[] OnWorkflowDispatchOptionalInputs { get; set; } = new string[0]; +``` + +**Rules:** + +- **Adding `[Obsolete]` is not a breaking change.** It's warning-level by default, so existing code keeps compiling — this is why it's preferred over a hard break. The *removal* is the break, and it's batched to the next yearly major. State the removal target in the message (e.g. `Removed in 2027.x.x.`). +- **Always set a `DiagnosticId`.** Without one the compiler reports the generic `CS0618`, so a `TreatWarningsAsErrors` consumer can only fix every usage at once or blanket-`NoWarn` all deprecations. A per-deprecation `FALLOUTOBS0xx` ID lets them suppress just this one while they migrate. +- **Diagnostic-ID scheme: `FALLOUTOBS0xx`.** Allocated **sequentially and never reused**, from a sequence **separate** from the `FALLOUT0xx` used by `[Experimental]`. Register every allocation in the [diagnostic-ID registry](../obsolete_apis.md) in the same PR that introduces the attribute. +- **Keep the deprecated surface functional.** Prefer bridging the old member to the new one (e.g. fold legacy arrays into the typed replacement) over leaving it inert, and suppress the internal bridge usage with `#pragma warning disable` scoped to the exact ID. + ## CI pipeline & triggers Shaped by [milestone #18](https://github.com/Fallout-build/Fallout/milestone/18) and the [ADR-0004](../adr/0004-calendar-versioning-and-dual-pace-channels.md) ladder (amended by [ADR-0008](../adr/0008-collapse-experimental-into-main.md), which collapsed `experimental` into `main`). Invariants: diff --git a/docs/obsolete_apis.md b/docs/obsolete_apis.md new file mode 100644 index 00000000..e2124252 --- /dev/null +++ b/docs/obsolete_apis.md @@ -0,0 +1,54 @@ +# Obsolete APIs + +Fallout deprecates public APIs with [`System.ObsoleteAttribute`](https://learn.microsoft.com/dotnet/api/system.obsoleteattribute) and gives each deprecation a stable **`DiagnosticId`** so consumers can suppress *that one* deprecation without silencing every `CS0618`. This page is the **canonical registry of allocated `FALLOUTOBS0xx` diagnostic IDs**. + +This is the counterpart to the [`[Experimental]` registry](experimental-apis.md): `[Experimental]` gates *not-yet-stable* surface you opt into (error-by-default); `[Obsolete]` marks *on-the-way-out* surface that still works (warning-by-default). The two use **separate ID sequences** — `FALLOUTOBS0xx` here, `FALLOUT0xx` there — so a suppression can never cross the two. + +See [the agent conventions](agents/conventions.md#obsolete-for-deprecating-public-apis) for the contributor rules. + +## How it works + +A deprecated API carries a message, a diagnostic ID, and a help URL: + +```csharp +using System; + +[Obsolete( + "Use [GitHubActionsInputAttribute] instead. Removed in 2027.x.x.", + DiagnosticId = "FALLOUTOBS001", + UrlFormat = "https://github.com/Fallout-build/Fallout/blob/main/docs/obsolete_apis.md")] +public string[] OnWorkflowDispatchOptionalInputs { get; set; } = new string[0]; +``` + +`ObsoleteAttribute` is **warning-by-default**: consumers keep compiling, but see the deprecation. Without a `DiagnosticId` the compiler reports the generic `CS0618`; setting one makes it report `FALLOUTOBS001` instead, which is what lets a consumer suppress a single deprecation: + +```xml + + $(NoWarn);FALLOUTOBS001 + +``` + +This matters for consumers building with `TreatWarningsAsErrors`: without a per-deprecation ID their only options are to fix every usage at once or blanket-disable all of `CS0618`. `DiagnosticId` gives them a targeted, acknowledge-and-move-on escape hatch while they migrate. `UrlFormat` (optional; `{0}` is replaced with the diagnostic ID when present) points at this registry so the warning links to the migration path. + +## Diagnostic-ID scheme + +- IDs use the form `FALLOUTOBS0xx` and are allocated **sequentially**. +- The `FALLOUTOBS0xx` sequence is **independent** of the `FALLOUT0xx` sequence used by [`[Experimental]`](experimental-apis.md) — allocate from this registry only. +- An ID is **never reused** — once retired it stays retired, so a consumer's `NoWarn` can never silently re-bind to a different deprecation. +- Every allocation is recorded in the registry table below, in the same PR that introduces the attribute. +- **Adding `[Obsolete]` is not a breaking change** — a warning-level deprecation keeps existing code compiling. The break is *removing* the API, which is batched to the next yearly major (see [AGENTS.md rule #2](../AGENTS.md) and the [release/versioning policy](agents/release-and-versioning.md)). Record the removal target in the message and the registry so consumers can plan. +- **When the API is finally removed**, its row moves to **Removed** status and the ID is retired, not recycled. + +## Registry + +Status values: **Deprecated** (live, warns on use), **Removed** (API deleted — ID retired). + +| ID | Surface | Deprecated | Status | Notes | +|----|---------|------------|--------|-------| +| _none yet_ | | | | | + +