|
| 1 | +# API stability and deprecation policy |
| 2 | + |
| 3 | +This document is the compatibility contract for `base-cli`. It applies to the |
| 4 | +current pre-1.0 releases and will be updated before the 1.0 release. The |
| 5 | +repository's contract tests and release checklist are expected to change with |
| 6 | +this document, so a policy change is itself a reviewed, changelogged change. |
| 7 | + |
| 8 | +## Public surface |
| 9 | + |
| 10 | +The supported Python facade is `import base_cli`. The names in |
| 11 | +`base_cli.__all__` are the public facade; documented names in the explicitly |
| 12 | +exported modules (`base_cli.command_protocol`, `base_cli.json_contracts`, and |
| 13 | +the other modules listed in their module-level `__all__`) are public as well. |
| 14 | +Names beginning with `_`, modules not documented here, and implementation |
| 15 | +details are private and may change without notice. |
| 16 | + |
| 17 | +The following machine-facing contracts are public and versioned: |
| 18 | + |
| 19 | +- command framing defaults to `COMMAND_PROTOCOL_V1`; record schemas are owned |
| 20 | + and registered by the consumer; |
| 21 | +- JSON output, error, and log records use `schema_version: 1` and the schema |
| 22 | + identifiers documented in [`json-contracts.md`](json-contracts.md); and |
| 23 | +- the exported callable and type signatures exercised by the public API tests. |
| 24 | + |
| 25 | +Human-readable messages, table layout, log wording, temporary directory names, |
| 26 | +and history implementation details are not stable machine interfaces unless a |
| 27 | +separate contract document says otherwise. Consumers that need automation |
| 28 | +should select the JSON or record protocol contracts. |
| 29 | + |
| 30 | +`base_cli.experimental` is reserved for preview APIs. No experimental symbols |
| 31 | +are currently shipped. A future preview must live under that namespace, be |
| 32 | +labelled experimental in its documentation, and must not be re-exported from |
| 33 | +the stable facade until it is promoted. |
| 34 | + |
| 35 | +## Versioning and compatibility |
| 36 | + |
| 37 | +Starting with 1.0, `base-cli` follows Semantic Versioning: |
| 38 | + |
| 39 | +- a **MAJOR** release may remove or change public APIs and versioned contracts; |
| 40 | +- a **MINOR** release adds backwards-compatible functionality and may begin a |
| 41 | + deprecation; and |
| 42 | +- a **PATCH** release contains compatible fixes, security fixes, and docs. |
| 43 | + |
| 44 | +Until 1.0, the leading zero is meaningful: patch releases remain compatible, |
| 45 | +while a minor release is a compatibility boundary and may contain a breaking |
| 46 | +change. We still prefer additive changes, and any pre-1.0 break must include a |
| 47 | +warning where practical, a migration path, and a changelog entry. Consumers |
| 48 | +that need a frozen API should pin a minor release (for example, `~=0.3.0`). |
| 49 | + |
| 50 | +The core package requires Python `>=3.10` and currently tests CPython 3.10 |
| 51 | +through 3.14 on Linux, macOS, and Windows. Core runtime dependencies are |
| 52 | +Click `>=8.1` and PyYAML `>=6.0`. Optional integrations are independently |
| 53 | +versioned and constrained in `pyproject.toml`: Typer `>=0.12,<0.26`, Rich |
| 54 | +`>=13.7,<15`, and OpenTelemetry API `>=1.24,<2`. The lower bounds are the |
| 55 | +minimum supported versions; a dependency major release is supported after it |
| 56 | +passes the compatibility suite. A future minor release may drop an end-of-life |
| 57 | +Python or dependency window with a migration note. |
| 58 | + |
| 59 | +Platform tier details and the operating-system support test matrix are kept in |
| 60 | +[`platform-support.md`](platform-support.md). |
| 61 | + |
| 62 | +## Deprecation process |
| 63 | + |
| 64 | +Use the public `base_cli.deprecated` decorator for callable APIs: |
| 65 | + |
| 66 | +```python |
| 67 | +from base_cli import deprecated |
| 68 | + |
| 69 | + |
| 70 | +@deprecated("0.3", remove="1.0", alternative="new_name") |
| 71 | +def old_name(value: str) -> str: |
| 72 | + return new_name(value) |
| 73 | +``` |
| 74 | + |
| 75 | +The decorator preserves the callable's metadata and behavior, and emits a |
| 76 | +`BaseCliDeprecationWarning` with `stacklevel=2` on every call. Applications can |
| 77 | +show or fail on these warnings with the standard `warnings` filters. The |
| 78 | +warning identifies the release that introduced the deprecation, the planned |
| 79 | +removal release, and (when available) the replacement API. |
| 80 | + |
| 81 | +Every deprecation must: |
| 82 | + |
| 83 | +1. remain supported for at least **two minor releases and 90 calendar days, |
| 84 | + whichever is longer**; |
| 85 | +2. include a migration note in [`migrations.md`](migrations.md) or the relevant |
| 86 | + contract document; |
| 87 | +3. appear in `CHANGELOG.md` under the release that introduces the warning and |
| 88 | + the release that removes the API; and |
| 89 | +4. be removed only in the stated removal release (or a later release), except |
| 90 | + for an urgent security or legal fix that is explicitly documented. |
| 91 | + |
| 92 | +The removal PR must delete the contract test for the old symbol only after the |
| 93 | +replacement and migration guidance are present. A deprecation is not complete |
| 94 | +until the warning, docs, tests, and changelog agree. |
| 95 | + |
| 96 | +## Contract guardrails |
| 97 | + |
| 98 | +`tests/test_public_api.py` verifies that every facade export is resolvable, |
| 99 | +that module `__all__` surfaces do not silently drift, and that private legacy |
| 100 | +names stay absent. `tests/test_api_stability.py` additionally verifies the |
| 101 | +warning behavior, the default command-protocol header, and the JSON v1 schema |
| 102 | +identifiers and envelope fields. Changes to an exported symbol or a versioned |
| 103 | +schema therefore require an intentional test and documentation update before |
| 104 | +release. |
| 105 | + |
| 106 | +When a schema must evolve incompatibly, add a new version and an adapter rather |
| 107 | +than changing the meaning of an existing field in place. Keep the old version |
| 108 | +available for the same deprecation window and document the migration. |
| 109 | + |
| 110 | +## Release checklist |
| 111 | + |
| 112 | +Before publishing a release, maintainers should confirm: |
| 113 | + |
| 114 | +- the supported Python and dependency windows still match CI and `pyproject.toml`; |
| 115 | +- public exports and schema constants have contract-test coverage; |
| 116 | +- each deprecation has a warning, removal target, migration note, and changelog |
| 117 | + entry; and |
| 118 | +- the release notes call out any pre-1.0 compatibility boundary or contract |
| 119 | + version addition. |
| 120 | + |
| 121 | +See [`releasing.md`](releasing.md) for the mechanical package and publishing |
| 122 | +steps. |
0 commit comments