Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
22 changes: 22 additions & 0 deletions docs/agents/conventions.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down Expand Up @@ -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:
Expand Down
54 changes: 54 additions & 0 deletions docs/obsolete_apis.md
Original file line number Diff line number Diff line change
@@ -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
<PropertyGroup>
<NoWarn>$(NoWarn);FALLOUTOBS001</NoWarn>
</PropertyGroup>
```

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_ | | | | |

<!--
Allocation example (do not uncomment unless a real API is deprecated):

| `FALLOUTOBS001` | `GitHubActionsAttribute.OnWorkflowDispatch{Optional,Required}Inputs`, `GitHubActionsWorkflowDispatchTrigger.{Optional,Required}Inputs` | 2026.x | Deprecated | Untyped `workflow_dispatch` input arrays. Use `[GitHubActionsInputAttribute]` / `GitHubActionsWorkflowDispatchTrigger.Inputs` instead. Removal target: 2027.0.0. |
-->