error-forge 1.0.0

Pragmatic Rust error-handling framework with stable error metadata, contextual diagnostics, optional async support, and synchronous recovery primitives (retry, circuit-breaker, backoff). Optional #[derive(ModError)], declarative define_errors!, and feature-gated logging / tracing / serde adapters.
Documentation
# Releasing error-forge

This document codifies the release procedure. Following it
guarantees that the git tag, `Cargo.toml` version, CHANGELOG
heading, release-note filename, and crates.io publication stay
in sync.

The procedure is the same for `error-forge` and the proc-macro
sub-crate `error-forge-derive` — they are versioned together,
and the derive crate must publish first because the main crate
depends on a specific `error-forge-derive` version.

## Versioning

`error-forge` follows [Semantic Versioning 2.0.0](https://semver.org/),
with the contract pinned in [`docs/STABILITY.md`](docs/STABILITY.md):

- **Patch (`1.x.Y`)** — bug fixes, doc improvements, internal
  performance work, test additions. No new public items.
- **Minor (`1.x.0`)** — pure additions to the public surface,
  new opt-in features, new variants on `#[non_exhaustive]`
  enums, new fields on `#[non_exhaustive]` structs, MSRV bumps.
- **Major (`X.0.0`)** — removal / rename / signature change of
  any public symbol, removing `#[non_exhaustive]`, adding a
  required runtime dep, breaking the documented migration path.

Pre-release suffixes use `-alpha.N`, `-beta.N`, `-rc.N`.

## Pre-release checklist

Before tagging:

- [ ] All planned work for the release is merged to `main`.
- [ ] `CHANGELOG.md` has a complete entry under the new version
      heading. Move items from `[Unreleased]`; reset
      `[Unreleased]` to empty placeholders.
- [ ] `Cargo.toml` `version` in **both** `./Cargo.toml` and
      `./error-forge-derive/Cargo.toml` matches the new version.
- [ ] The main crate's `[dependencies]` entry for
      `error-forge-derive` matches the new version exactly.
- [ ] `README.md` install snippet shows the new version.
- [ ] `.dev/release/vX.Y.Z.md` exists and describes the release.

## Local verification gate

Run every check below and confirm all green:

```bash
cargo fmt --all -- --check
cargo clippy --workspace --all-targets --all-features -- -D warnings
cargo clippy --workspace --all-targets --no-default-features -- -D warnings
cargo build --workspace --no-default-features
cargo build --workspace --all-features
cargo +1.81 build --workspace --all-features
cargo test --workspace --all-features
RUSTDOCFLAGS="-D warnings" cargo doc --workspace --all-features --no-deps
cargo audit
cargo publish -p error-forge-derive --dry-run
cargo publish -p error-forge --dry-run
```

Note: the main-crate dry-run will fail with
`failed to select a version for the requirement
error-forge-derive = "^X.Y.Z"` until the derive sub-crate is
actually published. That's expected — dry-run the main crate
*after* the derive crate is on crates.io.

## Tagging and pushing

```bash
git add Cargo.toml error-forge-derive/Cargo.toml \
        README.md CHANGELOG.md src/ docs/ benches/ examples/ \
        .dev/release/vX.Y.Z.md
git commit -m "Release vX.Y.Z"

git tag -a vX.Y.Z -m "Release vX.Y.Z"
git push origin main
git push origin vX.Y.Z
```

The git tag, `Cargo.toml` version, and the CHANGELOG heading
**MUST** match exactly. CI rejects releases where they
diverge.

## Publishing to crates.io

**The derive sub-crate must publish first** because the main
crate's `Cargo.toml` declares
`error-forge-derive = { version = "X.Y.Z", ... }` — that
version must be available on crates.io before the main crate
can publish.

```bash
# 1. Publish derive sub-crate
cargo publish -p error-forge-derive

# 2. Wait ~30 seconds for the crates.io sparse index to update.
#    (Verify via `cargo search error-forge-derive | head -3`.)

# 3. Publish main crate
cargo publish -p error-forge
```

If step 3 fails with "candidate versions found which didn't
match", the index hasn't caught up. Wait another minute and
retry.

## Post-release

- [ ] GitHub release created with body lifted from
      `.dev/release/vX.Y.Z.md`. Title: `vX.Y.Z — <summary>`.
- [ ] crates.io page renders (`https://crates.io/crates/error-forge`).
- [ ] docs.rs build succeeds
      (`https://docs.rs/error-forge/X.Y.Z`).
- [ ] CI is green on the release commit.

## Recovery procedures

### Publish failed mid-flight

The derive crate published but the main crate failed. **The
derive crate cannot be un-published** (crates.io only allows
yanking). Options:

1. Wait for the index to catch up, retry the main-crate publish.
2. If the derive crate has a real bug, yank it
   (`cargo yank --version X.Y.Z -p error-forge-derive`) and
   release a new patch version with the fix.

### Wrong version published

Yank, fix, release new version. **Never** delete tags or rewrite
history on `main`. Tagged versions are immutable.

```bash
cargo yank --version X.Y.Z -p error-forge
cargo yank --version X.Y.Z -p error-forge-derive
# Then bump and re-release as X.Y.(Z+1).
```

### CI was green locally but red on push

The most common causes:

- The `Cargo.lock` is format v4 (Rust 1.78+) but you tested on
  the `1.81.0` MSRV which can parse it; pushing to a fresh
  runner without your local cache exposes a difference. The
  MSRV job catches this.
- A platform-specific test that passed on your dev OS fails on
  another. The matrix catches this.
- A test depends on a tool not installed on the runner. Inspect
  the failed job logs (`gh run view <RUN_ID> --log-failed`).

Investigate, push a fix, and re-tag if necessary (only if the
new tag has not yet been pushed).

## Pre-1.0 history

Pre-1.0 release notes live in `.dev/release/v0.x.y.md`.
Pre-1.0 releases occasionally skipped this ceremony; `1.0.0`
formalises it.