ADR-14: Public-API deprecation policy (managed debt)

Status: accepted (2026-05-29) Blocks: workstream C (maturity), workstream E (bridge)

Context

The ecosystem house rule is “no dead code, no deprecated code, no backward-compatibility shims.” That rule is correct in steady state but wrong during a multi-release migration: replacing the nirs4all backend with dag-ml legitimately requires temporary compatibility code (a backend="legacy" path, dual-read bundle loaders, deprecated-but-still-working symbols). Codex flagged the blanket rule as counterproductive for the migration window.

Decision

For the migration window (until the G6 cutover completes), the absolute no-deprecated-code rule is replaced by a managed-debt policy. After G6, the absolute rule resumes.

Rules during the window:

  1. Every Rust #[deprecated(since = "X.Y.Z", note = "...")] must name a target removal version and link a tracking issue in the note.

  2. CI fails if any #[deprecated] lacks a removal version, or if a release whose version ≥ the declared removal version still contains the symbol.

  3. Every deprecated public symbol must carry a removal-test (asserts the deprecation fires and the replacement works) until it is removed.

  4. Production-path TODOs must be justified: TODO(owner): reason (#issue). Unexplained TODO/FIXME on a production path fails the CI lint.

  5. nirs4all’s public API (frozen since 0.9.0) follows the same rule plus a minimum two-release working window before any removal, because external consumers (nirs4all-studio) depend on it.

Consequences

  • A CI lint (scripts/check_deprecations.py, workstream C) parses #[deprecated] attributes and the TODO corpus and enforces rules 1–4.

  • The CHANGELOG records every new deprecation under “Deprecated” with its removal version, and every removal under “Removed.”

  • CONTRIBUTING.md documents the policy so a contributor knows a deprecation is a commitment, not a free escape hatch.

Risk

  • A managed-debt window can drift into permanent debt if removal versions slip. The CI gate (rule 2) is the backstop: a shipped removal version with the symbol still present is a hard failure, forcing the removal or an explicit ADR-superseder that re-schedules it.