Skip to content

ADR-036: Public surface stability policy

Status

Accepted — the stability policy is normative (#369, roadmap decision D6).

Context

Awa is consumed through distinct surfaces — the Rust API, the Python API, the SQL producer contract, HTTP admin API, callback receiver contract, metric names/attributes, and the CLI — and 0.6's beta-series churn (QueueFanoutPartitionedQueue) showed the cost of having no written boundary: consumers cannot tell a supported surface from an internal one, and maintainers cannot tell a breaking change from a refactor. Polyglot producers (#342) and API consumers cannot exist without a written contract. Accepted designs such as ADR-042's SQL worker finalization contract join this map only when implementation, public documentation and the surface table, conformance evidence, and the changelog transition ship together; an upgrade guide is also required when a new surface replaces an older covered entry point.

Decision

Publish and maintain the stability policy as the single normative statement of what is stable:

  • A surface-by-surface map: what each surface promises, what is explicitly internal. Anything unlisted is internal and may change in any release without notice.
  • Release-type rules while pre-1.0: patches fix bugs only (no new migrations except to repair a defective one); minors (0.x → 0.x+1) play the semver-major role and may break listed surfaces only with a changelog entry, an upgrade path, and — where feasible — a deprecation period; pre-release tags of one line promise nothing to each other.
  • A deprecation policy: announce in N, warn at runtime in N+1 where possible, remove in N+2; security fixes may compress the schedule explicitly.
  • A binary/schema skew statement, asserted nightly by the compat matrix (scripts/compat-matrix.sh, #367) against pinned release artifacts.

Changes to the policy are made by amending the document in an ordinary reviewed PR; release notes list breaking changes against its surface list. If a change breaks something unlisted and that surprises users repeatedly, the fix is to amend the document, not to argue.

Consequences

Positive

  • Consumers get an explicit contract per surface; maintainers get an explicit licence to change everything else.
  • Enforcement has concrete hooks: cargo-semver-checks for the Rust rows (#402), the compat matrix for skew, --json schema tests for the CLI, and the release checklist in the #383 tracker.

Negative

  • The document must move with the code: every PR that adds a --json command, a probe field, or a public type either updates it or implicitly leaves that surface internal.
  • Promising the HTTP response types constrains handler refactors until the response-type module (#403) fully separates them from the DAL.

Alternatives considered

  • Semver alone — says that something broke, not which surface was ever promised; pre-1.0 semver carries no information for consumers.
  • A stability attribute per item (Rust #[stable]-style annotations) — heavier tooling than a surface map justifies, and it cannot describe SQL, HTTP, metric, or CLI surfaces at all.
  • Per-crate CHANGELOG discipline without a map — documents changes without defining the boundary; the boundary is the thing consumers need.

Relationship to other ADRs

Governs the ADR-016 adapter contract, the #342 SQL producer contract, and ADR-042 only after its implementation, public documentation/table row, conformance evidence, and changelog transition ship together (plus an upgrade guide when replacing an older surface); constrains every future ADR that touches a listed surface; the skew row is enforced by the ADR-037 migrate gate plus the compat matrix.