# Versioning Policy & Deprecations Tracker
This document defines what counts as a breaking change in swarph-mesh, the deprecation cycle policy, and the live tracker of deprecated surfaces.
Why this document exists
swarph-mesh is the graph protocol substrate that third-party CLIs and future enterprise tooling plug into via the LLMAdapter Protocol, MeshClient wire format, and discovery primitives. These contracts are external-facing β silent changes orphan node implementers.
This file makes the contract explicit:
- What surfaces are part of the public contract
- What constitutes a breaking change to each
- How long deprecation must run before removal
- The current live deprecations and their removal timelines
Pairs with [SECURITY.md](./SECURITY.md), which lists "Protocol contract violation" as a reportable security class β meaning shipping a breaking change without honoring the policy in this document is itself a security incident.
Semantic versioning policy
swarph-mesh follows [Semantic Versioning 2.0.0](https://semver.org/spec/v2.0.0.html) with one tightening:
- MAJOR (X.0.0): any backwards-incompatible change to the public contract surface.
- MINOR (0.X.0): backwards-compatible additions to public contract; can include backwards-compatible deprecations announced for removal in a future MAJOR.
- PATCH (0.0.X): backwards-compatible bug fixes only. NO deprecation announcements, NO new public API.
We're currently in 0.x zone where some loosening is allowed: 0.MINOR.PATCH may add API in MINOR, fix bugs in PATCH. LLMAdapter Protocol changes still require deprecation cycle even pre-1.0 because external node implementers exist.
When 1.0.0 ships, all rules tighten to standard semver.
What counts as the public contract
Each module/symbol below is part of the contract. Changes to these require deprecation cycle.
Tier 1 (HIGHEST stability β third-party node-implementer-facing)
swarph_mesh.types.LLMAdapterProtocol β methods, signatures, kwarg defaults, return typesswarph_mesh.types.ChatMessage+LLMResponsefield sets- Canonical adapter names:
{gemini, deepseek, claude, openai, grok}reserved; cannot be renamed without major version bump MeshClient.send+MeshClient.fetchsignatures (third-party callers depend on the kwarg shape)MeshMessagewire shape (claude_messages JSONL persistence depends on this)discovery.list_models/is_model_supported/get_model_infosignaturesdiscovery.fetch__pricing+fetch__cost_buckets+reconcile_*_costsignatures
Tier 2 (MEDIUM stability β consumer-facing but internal-friendly)
SwarphCallpublic methods + kwargsAttributionWriterProtocol +FileAttributionWriter/NullAttributionWriterdefaultsHookSet+CallContextdataclassesdiscovery.normalize_*_idhelper signatures (drop DM #745 obs #1)discovery._RETIREMENT_REGISTRYkeys (consumers may iterate)
Tier 3 (LOWER stability β best-effort but documented)
- Adapter-local
PRICINGdicts (consumers should callcost_per_token()instead of importing PRICING directly, but some do β flagged inSECURITY.mdas cost-attribution concern) _normalize_*_idadapter-local copies (preserved for back-compat per drop #745; eventual removal once centralized helpers settle)discovery._ANTHROPIC_PRICINGstatic table contents
NOT part of the contract (no stability guarantee)
- Module-level private
_*symbols (registry caches, helpers) - Test fixtures, internal utility functions
- Mesh-gateway database schema (separate repo, separate semver)
- swarph-shared interface (separate package; tracked in its own DEPRECATIONS)
What counts as a breaking change
For each Tier 1/2 surface:
LLMAdapter Protocol
- Adding a method β BREAKING (third-party implementations now fail
isinstance(x, LLMAdapter)). Required deprecation: announce in MINOR, ship with default no-op or fallback behavior, removeisinstancerequirement enforcement until next MAJOR. - Removing a method β BREAKING. Required deprecation: announce removal in MINOR-1, log
DeprecationWarningfor 1 minor cycle, remove in next MINOR. - Renaming a method β ALWAYS BREAKING. Treat as add+remove with deprecation cycle on the old name.
- Changing a method signature (kwarg removal, kwarg-default change, return-type narrowing) β BREAKING. Add new method, deprecate old, remove old in next MINOR cycle.
- Adding a method with default behavior (e.g.,
list_models()in v0.6.0 with discovery delegation) β STILL BREAKING under runtime-checkable Protocol semantics, but minimally so. Treated as MINOR with deprecation announcement that mocks/stubs need updating.
MeshMessage / MeshClient wire format
- Adding a field with default β NON-BREAKING (extra: allow per pydantic config).
- Removing a field β BREAKING. Deprecation cycle: 1 minor of
DeprecationWarningonmodel_dump(), then remove. - Making an optional field required β BREAKING. Same cycle.
- Renaming a field β ALWAYS BREAKING. Add new + deprecate old + remove old in next MINOR.
Canonical adapter names
- Renaming a canonical name (e.g.,
geminiβgoogle) β MAJOR-version-only change. No deprecation path that doesn't break existing string-pinned consumers. - Adding a new canonical name β NON-BREAKING.
- Removing a canonical name (deprecating a provider entirely) β MINOR with
DeprecationWarningfor 2 cycles, then MAJOR.
Deprecation cycle requirements
1. Announcement: PR introducing the deprecation must update this file's "Active deprecations" section AND add a DeprecationWarning in the relevant code path.
2. Cycle length: minimum 1 MINOR release where the old surface emits warnings AND continues to work.
3. Removal: in the MINOR after the announcement (or later β never sooner).
4. CHANGELOG entry: every deprecation announcement and every removal lands in CHANGELOG.md.
5. Migration guide: deprecations affecting Tier 1 surfaces require a migration paragraph in this file.
Active deprecations
_None at v0.7.2._
When a deprecation lands, this section gains an entry like:
`
deprecated_method_name (since v0.X.Y, removal in v0.X+1.0)
What: Old surface description.
Why: Reason for deprecation (replaced by Z, security tightening, etc.).
Migration: Concrete code example oldβnew.
Status: Announced 2026-MM-DD. DeprecationWarning live since v0.X.Y.
Removal scheduled for v0.X+1.0.
`
Removed surfaces (historical)
_None β swarph-mesh has not yet removed any public surface._
When a removal happens, this section records it for archaeology:
`
method_name (removed in v0.X.0)
Was: Old surface description.
Replaced by: New surface, if any.
Final deprecation cycle: v0.Y.Z β v0.X.0.
`
Forward-references in v0.7.0 / v0.7.1 (resolved by this document)
SECURITY.mdΒ§ 7 (Protocol contract violation): "shipped without a deprecation window perDEPRECATIONS.md" β policy now lives here.tests/test_protocol_contract.pytest failure messages: "If intentional, updatePROTOCOL_FROZEN_AT_VERSION+ add entry toDEPRECATIONS.md" β the entry format is "Active deprecations" above.- CI workflow
mypy --strictratchet comment: "each minor version tightens one additional module per theDEPRECATIONS.mdratcheting discipline" β ratchet schedule documented in next section.
Mypy --strict module ratchet schedule
The CI gate (.github/workflows/ci.yml) currently runs mypy --strict on types.py + exceptions.py. Each subsequent minor tightens one more module:
| Version | Module added to strict gate | Status |
|---|---|---|
| v0.7.0 | types.py, exceptions.py | β
initial |
| v0.7.1 | (none β security policy release) | β
|
| v0.7.2 | (none β versioning policy release) | β
|
| v0.7.3 | (none β Sphinx docs release; doc surface itself is the hardening) | β
|
| v0.7.4 | discovery.py (after type-tightening pass) | planned |
| v0.7.5 | __init__.py + mesh_client.py | planned |
| v0.7.6 | swarph_call.py + attribution.py + hooks.py | planned |
| v0.8.0 | adapters/*.py (per-adapter strict in major bump window) | planned |
When a module is added to the strict gate, the corresponding type-tightening (e.g., dict β dict[str, Any]) is part of that release's PR, NOT a follow-up. Tightening type annotations is non-breaking (more specific β not less specific).
Hardening commitment per release (anchor point)
SECURITY.md commits to "each minor release tightens at least one of: secret-guard regexes, protocol-stability snapshots, mypy strict-module ratchet, privilege-boundary tests."
Tracked here:
| Version | Hardening dimension | What | |---|---|---| | v0.7.0 | protocol-stability + mypy | Initial 10 snapshot tests + types.py/exceptions.py strict gate | | v0.7.1 | secret-guard | (none β governance release; v0.7.4 includes regex audit) | | v0.7.2 | governance | DEPRECATIONS.md anchors policy referenced by SECURITY.md | | v0.7.3 | doc-surface | Sphinx auto-docs + GH Pages strict-mode build forces autodoc miss = build break, keeping documented contract in lockstep with code | | v0.7.4 | coverage-surface | Codecov patch coverage gate at 70% β fresh uncovered code blocks PR merge; project regression tolerance 1%; baseline 90% line / 88% branch on 1316 statements | | v0.7.5+ | TBD per release | tracked here |
How to file a deprecation
1. Open a PR adding entry to "Active deprecations" above.
2. Code change emits warnings.warn("...", DeprecationWarning, stacklevel=2) at the deprecated callsite.
3. PR description names the removal target version.
4. CHANGELOG.md "Unreleased" gets the announcement entry.
5. After the announcement ships, the removal PR (in a later release) moves the entry from "Active deprecations" to "Removed surfaces" in this file + removes the code.
When in doubt
If you're unsure whether a change is breaking, the default is YES. Open a PR with a deprecation cycle. The cost of an unnecessary deprecation cycle is one minor release of warnings; the cost of a silent breaking change is third-party node implementers losing trust in the contract.
That trust is the load-bearing thing. Protect it.